/*
 * Copyright 2013 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.servlet.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.ocpsoft.common.util.Streams;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.logging.Logger.Level;
import org.ocpsoft.rewrite.AbstractRewrite;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.rewrite.servlet.RewriteLifecycleContext;
import org.ocpsoft.rewrite.servlet.RewriteWrappedResponse;
import org.ocpsoft.rewrite.servlet.config.response.ResponseContent;
import org.ocpsoft.rewrite.servlet.config.response.ResponseContentInterceptor;
import org.ocpsoft.rewrite.servlet.config.response.ResponseStreamWrapper;
import org.ocpsoft.rewrite.servlet.event.BaseRewrite.ServletRewriteFlow;
import org.ocpsoft.rewrite.servlet.event.OutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.spi.OutboundRewriteProducer;
import org.ocpsoft.rewrite.servlet.spi.RewriteLifecycleListener;
import org.ocpsoft.rewrite.spi.RewriteProvider;
import org.ocpsoft.urlbuilder.Address;
import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * @author Lincoln Baxter, III
 */
public class HttpRewriteWrappedResponse extends RewriteWrappedResponse
{
   private final HttpServletRequest request;

   private final ServletContext servletContext;

   private static final Logger log = Logger.getLogger(HttpRewriteWrappedResponse.class);

   public HttpRewriteWrappedResponse(final HttpServletRequest request, final HttpServletResponse response,
            final ServletContext servletContext)
   {
      super(request, response);
      this.request = request;
      this.servletContext = servletContext;

      if (getCurrentInstance(request) == null) {
         super.setCurrentInstance(this);
      }
   }

   /*
    * Buffering Facilities
    */
   private final ByteArrayOutputStream bufferedResponseContent = new ByteArrayOutputStream();

   private List<ResponseContentInterceptor> responseContentInterceptors = new ArrayList<ResponseContentInterceptor>();
   private List<ResponseStreamWrapper> responseStreamWrappers = new ArrayList<ResponseStreamWrapper>();

   private boolean contentWritten = false;

   private PrintWriter printWriter;
   private ServletOutputStream outputStream = null;
   private ServletOutputStream wrappedOutputStream = null;

   @Override
   public boolean isResponseContentIntercepted()
   {
      return !responseContentInterceptors.isEmpty();
   }

   @Override
   public boolean isResponseStreamWrapped()
   {
      return !responseStreamWrappers.isEmpty();
   }

   @Override
   public void addContentInterceptor(ResponseContentInterceptor stage) throws IllegalStateException
   {
      if (areStreamsLocked())
      {
         throw new IllegalStateException(
                  "Cannot add output buffers to response once request processing has been passed to the application.");
      }
      this.responseContentInterceptors.add(stage);
   }

   @Override
   public void addStreamWrapper(ResponseStreamWrapper wrapper)
   {
      if (areStreamsLocked())
      {
         throw new IllegalStateException(
                  "Cannot add output stream wrappers to response once request processing has been passed to the application.");
      }
      this.responseStreamWrappers.add(wrapper);
   }

   private boolean areStreamsLocked()
   {
      return contentWritten;
   }

   private void lockStreams()
   {
      this.contentWritten = true;
   }

   /**
    * Cause any buffered {@link ServletResponse} content to be processed and flushed to the client.
    */
   @Override
   public void flushBufferedContent()
   {
      if (isResponseContentIntercepted())
      {
         try {
            bufferedResponseContent.close();

            ResponseContent buffer = new ResponseContentImpl(bufferedResponseContent.toByteArray(),
                     Charset.forName(getCharacterEncoding()));
            new ResponseContentInterceptorChainImpl(responseContentInterceptors).begin(new HttpBufferRewriteImpl(
                     request, this, servletContext), buffer);

            if (!Charset.forName(getCharacterEncoding()).equals(buffer.getCharset()))
               setCharacterEncoding(buffer.getCharset().name());

            ServletOutputStream outputStream = isResponseStreamWrapped() ? wrappedOutputStream : super
                     .getOutputStream();

            if (outputStream != null)
               Streams.copy(new ByteArrayInputStream(buffer.getContents()), outputStream);

            if (printWriter != null) {
               printWriter.close();
            }
         }
         catch (IOException e) {
            throw new RewriteException("Error occurred when flushing response content buffered by "
                     + responseContentInterceptors, e);
         }
      }
   }

   @Override
   public void finishStreamWrappers()
   {
      if (isResponseStreamWrapped())
      {
         HttpServletRewrite event = new HttpBufferRewriteImpl(request, this, servletContext);
         for (ResponseStreamWrapper wrapper : responseStreamWrappers) {
            wrapper.finish(event);
         }
      }
   }

   @Override
   public String toString()
   {
      if (isResponseContentIntercepted())
      {
         try {
            return bufferedResponseContent.toString(getCharacterEncoding());
         }
         catch (UnsupportedEncodingException e) {
            throw new RewriteException("Response accepted invalid character encoding " + getCharacterEncoding(), e);
         }
      }
      else
         return super.toString();
   }

   @Override
   public PrintWriter getWriter()
   {
      if (printWriter == null)
      {
         if (isResponseContentIntercepted())
         {
            printWriter = new PrintWriter(new OutputStreamWriter(bufferedResponseContent,
                     Charset.forName(getCharacterEncoding())), true);
         }
         else if (isResponseStreamWrapped())
         {
            printWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(),
                     Charset.forName(getCharacterEncoding())), true);
         }
         else
         {
            try {
               lockStreams();
               return super.getWriter();
            }
            catch (IOException e) {
               throw new RewriteException("Could not get response writer.", e);
            }
         }
      }

      return printWriter;
   }

   @Override
   public ServletOutputStream getOutputStream()
   {

      if (outputStream == null)
      {
         if (isResponseContentIntercepted())
         {
            outputStream = new RewriteServletOutputStream(bufferedResponseContent);
         }
         else
         {
            try {
               lockStreams();
               outputStream = super.getOutputStream();
            }
            catch (IOException e) {
               throw new RewriteException("Could not get response output stream.", e);
            }
         }

         if (isResponseStreamWrapped())
         {
            if (wrappedOutputStream == null)
            {
               HttpServletRewrite event = new HttpBufferRewriteImpl(request, this, servletContext);

               try {
                  OutputStream wrapped = super.getOutputStream();
                  for (ResponseStreamWrapper wrapper : responseStreamWrappers) {
                     wrapped = wrapper.wrap(event, wrapped);
                  }
                  wrappedOutputStream = new RewriteServletOutputStream(wrapped);
                  if (!isResponseContentIntercepted())
                  {
                     outputStream = wrappedOutputStream;
                  }
               }
               catch (IOException e) {
                  throw new RewriteException("Could not get response output stream.", e);
               }

            }
         }
      }

      return outputStream;
   }

   @Override
   public void setContentLength(int contentLength)
   {
      lockStreams();
      /*
       * Prevent content-length being set as the page might be modified.
       */
      if (!isResponseContentIntercepted())
      {
         if (isResponseStreamWrapped())
         {
            setHeader("X-Uncompressed-Content-Length", String.valueOf(contentLength));
         }
         else
            super.setContentLength(contentLength);

      }
   }

   @Override
   public void flushBuffer() throws IOException
   {
      if (isResponseContentIntercepted())
         bufferedResponseContent.flush();
      else
      {
         lockStreams();
         super.flushBuffer();
      }
   }

   /**
    * Buffered {@link ServletOutputStream} implementation.
    *
    * @author Lincoln Baxter, III
    */
   private class RewriteServletOutputStream extends ServletOutputStream
   {
      private OutputStream stream;

      public RewriteServletOutputStream(OutputStream outputStream)
      {
         this.stream = outputStream;
      }

      @Override
      public void write(int b)
      {
         try {
            stream.write(b);
         }
         catch (IOException e) {
            throw new RewriteException("Error writing int to stream [" + stream + "]", e);
         }
      }

      @Override
      public void write(byte[] bytes) throws IOException
      {
         stream.write(bytes);
      }

      @Override
      public void write(byte[] bytes, int off, int len)
      {
         try {
            stream.write(bytes, off, len);
         }
         catch (IOException e) {
            throw new RewriteException("Error writing bytes to stream [" + stream + "] at offset [" + off
                     + "] with length [" + len + "]", e);
         }
      }

      @Override
      public String toString()
      {
         return stream.toString();
      }
   }

   /*
    * End buffering facilities
    */
   @Override
   public String encodeRedirectUrl(final String url)
   {
      return encodeRedirectURL(url);
   }

   @Override
   public String encodeUrl(final String url)
   {
      return encodeURL(url);
   }

   @Override
   public String encodeRedirectURL(final String url)
   {
      Address address = AddressBuilder.create(url);
      OutboundServletRewrite<ServletRequest, ServletResponse, Address> event = rewrite(address);

      if (event.getFlow().is(ServletRewriteFlow.ABORT_REQUEST))
      {
         return event.getOutboundAddress().toString();
      }
      return super.encodeRedirectURL(event.getOutboundAddress().toString());
   }

   @Override
   public String encodeURL(final String url)
   {

      /*
       * In some situations "url" may be not valid according to the rules defined in the RFC.
       * In this case AddressBuilder.create() will fail. In these situations we basically
       * skip outbound rewriting and just return the result of the super class.
       */
      Address address;
      try
      {
         address = AddressBuilder.create(url);
      }
      catch (IllegalArgumentException e) {
         log.warn("Skipping outbound rewriting of invalid URL: " + url);
         return super.encodeURL(url);
      }

      OutboundServletRewrite<ServletRequest, ServletResponse, Address> event = rewrite(address);

      if (event.getFlow().is(ServletRewriteFlow.ABORT_REQUEST))
      {
         return event.getOutboundAddress().toString();
      }
      return super.encodeURL(event.getOutboundAddress().toString());

   }

   @SuppressWarnings({ "unchecked", "rawtypes" })
   private OutboundServletRewrite<ServletRequest, ServletResponse, Address> rewrite(Address address)
   {
      OutboundServletRewrite<ServletRequest, ServletResponse, Address> event = null;
      try {
         RewriteLifecycleContext<ServletContext> context = (RewriteLifecycleContext<ServletContext>) request
                  .getAttribute(RewriteLifecycleContext.LIFECYCLE_CONTEXT_KEY);

         for (OutboundRewriteProducer producer : context.getOutboundProducers()) {
            if (producer.handles(address))
            {
               event = ((OutboundRewriteProducer<ServletRequest, ServletResponse, Address>) producer)
                        .createOutboundRewrite(request, getResponse(), servletContext, address);
            }
         }

         if (event == null)
         {
            log.warn("No instance of [" + OutboundServletRewrite.class
                     + "] was produced. Rewriting is disabled on this outbound event.");
         }
         else
         {
            for (RewriteLifecycleListener<Rewrite> listener : context.getRewriteLifecycleListeners())
            {
               listener.beforeOutboundRewrite(event);
            }

            for (RewriteProvider<ServletContext, Rewrite> p : context.getRewriteProviders())
            {
               if (p.handles(event))
               {
                  p.rewrite(event);
                  if (event.getFlow().is(ServletRewriteFlow.HANDLED))
                  {
                     break;
                  }
               }
            }

            for (RewriteLifecycleListener<Rewrite> listener : context.getRewriteLifecycleListeners())
            {
               listener.afterOutboundRewrite(event);
            }
         }

         AbstractRewrite.logEvaluatedRules(event, Level.DEBUG);

         return event;
      }
      catch (RuntimeException e)
      {
         if (event != null)
            AbstractRewrite.logEvaluatedRules(event, Level.ERROR);

         throw e;
      }
   }

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

   @Override
   public void sendError(int sc) throws IOException
   {
      lockStreams();
      super.sendError(sc);
   }

   @Override
   public void sendRedirect(String location) throws IOException
   {
      lockStreams();
      super.sendRedirect(location);
   }

   @Override
   public void reset()
   {
      bufferedResponseContent.reset();
      super.reset();
   }

   @Override
   public void resetBuffer()
   {
      bufferedResponseContent.reset();
      super.resetBuffer();
   }

}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocpsoft.pretty;

import java.io.IOException;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.context.FacesContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ocpsoft.pretty.faces.config.PrettyConfig;
import com.ocpsoft.pretty.faces.config.PrettyConfigurator;
import com.ocpsoft.pretty.faces.config.mapping.UrlMapping;
import com.ocpsoft.pretty.faces.url.QueryString;
import com.ocpsoft.pretty.faces.url.URL;
import com.ocpsoft.pretty.faces.util.Assert;

/**
 * @author Lincoln Baxter, III <lincoln@ocpsoft.com>
 */
public class PrettyContext implements Serializable
{

   private static final String DEFAULT_ENCODING = "UTF-8";
   public static final String CONFIG_KEY = "com.ocpsoft.pretty.CONFIG_FILES";

   private static final Log log = LogFactory.getLog(PrettyContext.class);
   private static final long serialVersionUID = -4593906924975844541L;

   public static final String PRETTY_PREFIX = "pretty:";
   private static final String CONTEXT_REQUEST_KEY = "prettyContext";

   private static final Pattern JSESSIONID_PATTERN = Pattern.compile("(?i)^(.*);jsessionid=[\\w\\.\\-\\+]+(.*)");
   private static final String JSESSIONID_REPLACEMENT = "$1$2";

   private PrettyConfig config;

   private final String contextPath;
   private URL requestURL;
   private final QueryString requestQuery;

   private UrlMapping currentMapping;

   private boolean dynaviewProcessed = false;
   private boolean inNavigation = false;

   /**
    * Must create instance through the initialize() method
    */
   protected PrettyContext(final HttpServletRequest request)
   {
      Assert.notNull(request, "HttpServletRequest argument was null");

      // attribute is set by PrettyFacesRewriteLifecycleListener before
      config = (PrettyConfig) request.getAttribute(CONFIG_KEY);

      // not sure if this can happen any more, but we'll keep it for now
      if (config == null)
      {
         config = new PrettyConfig();
      }

      contextPath = request.getContextPath();
      String requestUrl = stripContextPath(request.getRequestURI());
      Matcher sessionIdMatcher = JSESSIONID_PATTERN.matcher(requestUrl);
      if (sessionIdMatcher.matches())
      {
         requestUrl = sessionIdMatcher.replaceFirst(JSESSIONID_REPLACEMENT);
      }

      String encoding = request.getCharacterEncoding() == null ? DEFAULT_ENCODING : request.getCharacterEncoding();

      requestURL = new URL(requestUrl);
      requestURL.setEncoding(encoding);
      requestURL = requestURL.decode();

      requestQuery = QueryString.build(request.getQueryString());

      log.trace("Initialized PrettyContext");
   }

   /**
    * Get the current PrettyFaces context object, or construct a new one if it
    * does not yet exist for this request. (Delegates to
    * {@link FacesContext#getCurrentInstance()} to retrieve the current
    * {@link HttpServletRequest} object)
    *
    * @return current context instance
    */
   public static PrettyContext getCurrentInstance()
   {
      FacesContext context = FacesContext.getCurrentInstance();
      return getCurrentInstance(context);
   }

   /**
    * Get the current PrettyFaces context object, or construct a new one if it
    * does not yet exist for this request. (Delegates to {@link FacesContext} to
    * retrieve the current {@link HttpServletRequest} object)
    *
    * @return current context instance
    */
   public static PrettyContext getCurrentInstance(final FacesContext context)
   {
      Assert.notNull(context, "FacesContext argument was null.");
      return getCurrentInstance((HttpServletRequest) context.getExternalContext().getRequest());
   }

   /**
    * Get the current {@link PrettyContext}, or construct a new one if it does
    * not yet exist for this request.
    *
    * @return current context instance
    */
   public static PrettyContext getCurrentInstance(final HttpServletRequest request)
   {
      Assert.notNull(request, "HttpServletRequest argument was null");
      PrettyContext prettyContext = (PrettyContext) request.getAttribute(CONTEXT_REQUEST_KEY);
      if (prettyContext instanceof PrettyContext)
      {
         log.trace("Retrieved PrettyContext from Request");
         return prettyContext;
      }
      else
      {
         Assert.notNull(request, "HttpServletRequest argument was null");
         prettyContext = newDetachedInstance(request);
         log.trace("PrettyContext not found in Request - building new instance");
         setCurrentContext(request, prettyContext);
         return prettyContext;
      }
   }

   /**
    * Return true if there is an instantiated {@link PrettyContext} contained in
    * the current given request object.
    */
   public static boolean isInstantiated(final ServletRequest request)
   {
      Assert.notNull(request, "HttpServletRequest argument was null");
      PrettyContext prettyContext = (PrettyContext) request.getAttribute(CONTEXT_REQUEST_KEY);
      if (prettyContext instanceof PrettyContext)
      {
         return true;
      }
      else
      {
         return false;
      }
   }

   /**
    * Package private -- only {@link PrettyFilter} should be calling this method
    * -- it does not overwrite existing contexts in Request object
    */
   public static PrettyContext newDetachedInstance(final HttpServletRequest request)
   {
      Assert.notNull(request, "HttpServletRequest argument was null");

      PrettyContext prettyContext = new PrettyContext(request);
      return prettyContext;
   }

   /**
    * Package private -- only PrettyFilter or this class should be calling this
    * method -- it overwrites existing contexts in Request object
    */
   public static void setCurrentContext(final HttpServletRequest request, final PrettyContext prettyContext)
   {
      request.setAttribute(CONTEXT_REQUEST_KEY, prettyContext);
   }

   /**
    * Return the {@link URL} representing the path with which this request was
    * populated. (Does not include context-path.)
    */
   public URL getRequestURL()
   {
      return requestURL;
   }

   /**
    * Return the {@link QueryString} representing the query-string with which
    * this request was populated.
    *
    * @return
    */
   public QueryString getRequestQueryString()
   {
      return requestQuery;
   }

   /**
    * Determine if this request URL is mapped by PrettyFaces
    */
   public boolean isPrettyRequest()
   {
      return getCurrentMapping() != null;
   }

   public String getContextPath()
   {
      return contextPath;
   }

   /**
    * If the given URL is prefixed with this request's context-path, return the
    * URI without the context path. Otherwise return the URI unchanged.
    */
   public String stripContextPath(String uri)
   {
      if (!contextPath.equals("/") && uri.startsWith(contextPath))
      {
         uri = uri.substring(contextPath.length());
      }
      return uri;
   }

   /**
    * Get the pretty-config.xml configurations as loaded by
    * {@link PrettyConfigurator} (This can be dynamically manipulated at runtime
    * in order to change or add any mappings.
    */
   public PrettyConfig getConfig()
   {
      return config;
   }

   void setConfig(final PrettyConfig config)
   {
      this.config = config;
   }

   /**
    * Get the {@link UrlMapping} representing the current request.
    */
   public UrlMapping getCurrentMapping()
   {
      if (currentMapping == null)
      {
         currentMapping = config.getMappingForUrl(requestURL);
      }
      return currentMapping;
   }

   /**
    * Called once from PrettyFilter to initialize the currentMapping property.
    * This leads to better performance because the filter already knows the mapping
    * and we won't have to do the mapping identification again in the getter for
    * the property.
    */
   void setCurrentMapping(UrlMapping mapping)
   {
      currentMapping = mapping;
   }

   /**
    * Return the current viewId to which the current request will be forwarded
    * to JSF.
    */
   public String getCurrentViewId()
   {
      if (getCurrentMapping() != null)
      {
         return currentMapping.getViewId();
      }
      return "";
   }

   /**
    * Return whether or not this faces application is in the Navigation State
    */
   public boolean isInNavigation()
   {
      return inNavigation;
   }

   /**
    * Set whether or not to treat this request as if it is in the Navigation
    * State
    */
   public void setInNavigation(final boolean value)
   {
      inNavigation = value;
   }

   /**
    * @return True if the current mapping and request should trigger DynaView
    *         capabilities.
    */
   public boolean shouldProcessDynaview()
   {
      if (isPrettyRequest())
      {
         return getCurrentMapping().isDynaView() && !isDynaviewProcessed();
      }
      return false;
   }

   /**
    * Return true if the current request has already processed the DynaView
    * life-cycle.
    */
   public boolean isDynaviewProcessed()
   {
      return dynaviewProcessed;
   }

   public void setDynaviewProcessed(final boolean value)
   {
      dynaviewProcessed = value;
   }

   public String getDynaViewId()
   {
      return config.getDynaviewId();
   }

   /**
    *

    * Sends an error response to the client using the specified HTTP status
    * code.
    *

    *

    * Please note that this method can only be called from within the JSF
    * lifecycle as it needs the {@link FacesContext} to obtain the
    * {@link HttpServletResponse}. Please use
    * {@link #sendError(int, String, HttpServletResponse)} in all other
    * cases.
    *

    *
    * @param code the error status code
    * @see HttpServletResponse#sendError(int, String)
    */
   public void sendError(int code)
   {
      sendError(code, null);
   }

   /**
    *

    * Sends an error response to the client using the specified HTTP status
    * code.
    *

    *

    * Please note that this method can only be called from within the JSF
    * lifecycle as it needs the {@link FacesContext} to obtain the
    * {@link HttpServletResponse}. Please use
    * {@link #sendError(int, String, HttpServletResponse)} in all other
    * cases.
    *

    *
    * @param code the error status code
    * @param message the descriptive message
    * @see HttpServletResponse#sendError(int, String)
    */
   public void sendError(int code, String message)
   {
      FacesContext facesContext = FacesContext.getCurrentInstance();
      Assert.notNull(facesContext, "FacesContext argument was null.");

      Object response = facesContext.getExternalContext().getResponse();
      if (response instanceof HttpServletResponse)
      {
         sendError(code, message, (HttpServletResponse) response);
         facesContext.responseComplete();
      }

   }

   /**
    * Sends an error response to the client using the specified HTTP status
    * code.
    *
    * @param code the error status code
    * @param message the descriptive message
    * @param response
    * @see HttpServletResponse#sendError(int, String)
    */
   public void sendError(int code, String message, HttpServletResponse response)
   {
      Assert.notNull(response, "HttpServletResponse argument was null");
      try
      {
         if (message != null)
         {
            response.sendError(code, message);
         }
         else
         {
            response.sendError(code);
         }
      }
      catch (IOException e)
      {
         throw new IllegalStateException("Failed to send error code: " + code, e);
      }
   }

}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.ocpsoft.common.pattern.WeightedComparator;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.annotation.api.ClassContext;
import org.ocpsoft.rewrite.annotation.api.ClassVisitor;
import org.ocpsoft.rewrite.annotation.context.ClassContextImpl;
import org.ocpsoft.rewrite.annotation.context.FieldContextImpl;
import org.ocpsoft.rewrite.annotation.context.MethodContextImpl;
import org.ocpsoft.rewrite.annotation.context.ParameterContextImpl;
import org.ocpsoft.rewrite.annotation.spi.AnnotationHandler;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.config.Rule;

public class ClassVisitorImpl implements ClassVisitor, Configuration
{

   private final Logger log = Logger.getLogger(ClassVisitorImpl.class);

   /**
    * list of handlers for processing annotations
    */
   private final List<AnnotationHandler<Annotation>> handlerList;

   /**
    * The rules created by the visitor
    */
   private final ConfigurationBuilder builder = ConfigurationBuilder.begin();

   private Object payload;

   /**
    * The visitor must be initialized with the handlers to call for specific annotations
    */
   public ClassVisitorImpl(List<AnnotationHandler<Annotation>> handlers, Object payload)
   {
      handlerList = new ArrayList<AnnotationHandler<Annotation>>(handlers);
      Collections.sort(handlerList, new WeightedComparator());
      this.payload = payload;

      if (log.isDebugEnabled())
      {
         log.debug("Initialized to use {} AnnotationHandlers..", handlers.size());
      }

   }

   /**
    * Processes the annotation on the supplied class.
    */
   @Override
   public void visit(Class<?> clazz)
   {

      ClassContextImpl context = new ClassContextImpl(builder, clazz);
      context.put(clazz, payload);

      if (log.isTraceEnabled())
      {
         log.trace("Scanning class: {}", clazz.getName());
      }

      // first process the class
      visit(clazz, context);

      // only process fields and classes if a rule building has been started
      if (context.hasRuleBuildingStarted()) {

         // walk up the inheritance hierarchy
         Class<?> currentType = clazz;
         while (currentType != null) {

            // then process the fields
            for (Field field : currentType.getDeclaredFields())
            {
               visit(field, new FieldContextImpl(context, field));
            }

            // then the methods
            for (Method method : currentType.getDeclaredMethods())
            {
               MethodContextImpl methodContext = new MethodContextImpl(context, method);
               visit(method, methodContext);

               // then the method parameters
               for (int i = 0; i < method.getParameterTypes().length; i++)
               {
                  ParameterImpl parameter = new ParameterImpl(method, method.getParameterTypes()[i],
                           method.getParameterAnnotations()[i], i);
                  visit(parameter, new ParameterContextImpl(methodContext, parameter));
               }
            }

            currentType = currentType.getSuperclass();

         }
      }

   }

   /**
    * Process one {@link AnnotatedElement} of the class.
    */
   private void visit(AnnotatedElement element, ClassContext context)
   {

      List<AnnotationHandler<Annotation>> elementHandlers = new ArrayList<AnnotationHandler<Annotation>>();

      // check if any of the handlers is responsible
      for (AnnotationHandler<Annotation> handler : handlerList)
      {

         // each annotation on the element may be interesting for us
         for (Annotation annotation : element.getAnnotations())
         {
            if (handler.handles().equals(annotation.annotationType()))
            {
               elementHandlers.add(handler);
            }
         }

      }

      if (!elementHandlers.isEmpty())
      {

         if (log.isTraceEnabled())
         {
            log.trace("Executing handler chain on " + element + ": " + elementHandlers);
         }

         // execute the handler chain
         new HandlerChainImpl(context, element, elementHandlers).proceed();

      }
   }

   @Override
   public List<Rule> getRules()
   {
      return builder.getRules();
   }

}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.faces;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

import javax.faces.application.ConfigurableNavigationHandler;
import javax.faces.application.NavigationCase;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * @author Lincoln Baxter, III <lincoln@ocpsoft.com>
 */
public class RewriteNavigationHandler extends ConfigurableNavigationHandler
{
   private static final String REWRITE_PREFIX = "rewrite:";
   public static final String REDIRECT_PREFIX = "rewrite-redirect:";
   private static final String IN_NAVIGATION = RewriteNavigationHandler.class.getName() + "_inNavigation";
   private final ConfigurableNavigationHandler parent;

   public RewriteNavigationHandler(final ConfigurableNavigationHandler parent)
   {
      this.parent = parent;
   }

   @Override
   public void handleNavigation(final FacesContext context, final String fromAction, final String outcome)
   {
      if (!redirect(context, outcome))
      {
         setInNavigation((HttpServletRequest) context.getExternalContext().getRequest(), true);

         /*
          * the view root may be null if the navigation occurs before RESTORE_VIEW
          *
          * https://github.com/ocpsoft/rewrite/issues/142
          */
         String originalViewId = null;
         if(context.getViewRoot() != null)
         {
           originalViewId = context.getViewRoot().getViewId();
         }

         parent.handleNavigation(context, fromAction, outcome);

         /*
          * the view root may be null as a result of a ViewExpiredException
          *
          * https://github.com/ocpsoft/rewrite/issues/103
          */
         String newViewId = null;
         if (context.getViewRoot() != null)
         {
            newViewId = context.getViewRoot().getViewId();
         }

         /*
          * Navigation is complete if the viewId has not changed or the response is complete
          */
         if ((true == context.getResponseComplete()) || (originalViewId == newViewId) || (originalViewId != null && originalViewId.equals(newViewId)))
         {
            setInNavigation((HttpServletRequest) context.getExternalContext().getRequest(), false);
         }
      }
   }

   private void setInNavigation(final HttpServletRequest request, final boolean inNavigation)
   {
      request.setAttribute(IN_NAVIGATION, inNavigation);
   }

   public static boolean isInNavigation(final HttpServletRequest request)
   {
      Boolean inNavigation = (Boolean) request.getAttribute(IN_NAVIGATION);
      return inNavigation == null ? false : inNavigation;
   }

   @Override
   public NavigationCase getNavigationCase(final FacesContext context, final String fromAction, final String outcome)
   {
      // TODO integrate rewrite with navigation (See PrettyNavigationHandler)
      if (REWRITE_PREFIX.equals(outcome))
      {
         String viewId = context.getViewRoot().getViewId();
         NavigationCase navigationCase = parent.getNavigationCase(context, fromAction, viewId);
         return navigationCase;
      }
      else if (outcome != null && outcome.startsWith(REDIRECT_PREFIX)) {
         String url = outcome.substring(REDIRECT_PREFIX.length());
         String viewId = AddressBuilder.create(url).getPath();
         return parent.getNavigationCase(context, fromAction, viewId);
      }
      else
      {
         NavigationCase navigationCase = parent.getNavigationCase(context, fromAction, outcome);
         return navigationCase;
      }
   }

   private boolean redirect(final FacesContext context, final String outcome)
   {
      ExternalContext externalContext = context.getExternalContext();

      if (REWRITE_PREFIX.equals(outcome))
      {
         String target = FacesRewriteLifecycleListener.getOriginalRequestURL((HttpServletRequest) externalContext
                  .getRequest());
         String redirectUrl = externalContext.encodeRedirectURL(target, null);
         try {
            externalContext.redirect(redirectUrl);
            return true;
         }
         catch (IOException e) {
            throw new RewriteException("Could not redirect to [" + redirectUrl + "]", e);
         }
      }

      // outcomes created by Navigate for redirects
      else if (outcome != null && outcome.startsWith(REDIRECT_PREFIX)) {

         // strip the prefix to get the context-relative URL
         String relativeUrl = outcome.substring(REDIRECT_PREFIX.length());

         // add the context path
         String absoluteUrl = prependContextPath(externalContext, relativeUrl);

         // rewrite the URL
         String rewrittenUrl = externalContext.encodeActionURL(absoluteUrl);

         // send the redirect
         try {
            externalContext.redirect(rewrittenUrl);
            return true;
         }
         catch (IOException e) {
            throw new RewriteException("Could not redirect to [" + rewrittenUrl + "]", e);
         }

      }

      return false;
   }

   /**
    * Adds the context path to the given context-relative URL.
    */
   private String prependContextPath(ExternalContext externalContext, String url)
   {
      String contextPath = externalContext.getRequestContextPath();
      if ("/".equals(contextPath) || (url.startsWith(contextPath))) {
         return url;
      }
      return contextPath + url;
   }

   @Override
   public Map<String, Set<NavigationCase>> getNavigationCases()
   {
      return parent.getNavigationCases();
   }

   @Override
   public void performNavigation(final String outcome)
   {
      parent.performNavigation(outcome);
   }
}


/*
 * Copyright 2013 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.util;

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

import org.ocpsoft.rewrite.bind.Binding;
import org.ocpsoft.rewrite.bind.Evaluation;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.rewrite.param.DefaultParameter;
import org.ocpsoft.rewrite.param.DefaultParameterStore;
import org.ocpsoft.rewrite.param.Parameter;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;

/**
 * Utility methods for interactive with {@link ParameterStore} instances.
 *
 * @author Lincoln Baxter, III
 */
public class ParameterUtils
{
   /**
    * Initialize the {@link Parameterized} instance with the {@link ParameterStore} from the given
    * {@link EvaluationContext}, also record required parameter names in the {@link ParameterStore} and initialize with
    * a new {@link Parameter} instance.
    */
   public static void initialize(EvaluationContext context, Object parameterized)
   {
      initialize(DefaultParameterStore.getInstance(context), parameterized);
   }

   /**
    * Initialize the {@link Parameterized} instance with the given {@link ParameterStore}, also record required
    * parameter names in the {@link ParameterStore} and initialize with a new {@link Parameter} instance.
    */
   public static void initialize(ParameterStore store, Object parameterized)
   {
      if (parameterized instanceof Parameterized)
      {
         Set<String> names = ((Parameterized) parameterized).getRequiredParameterNames();
         for (String name : names) {
            store.get(name, new DefaultParameter(name));
         }
         ((Parameterized) parameterized).setParameterStore(store);
      }
   }

   /**
    * Submit the given value to all registered {@link Binding} instances of the given {@link Parameter}. Perform this by
    * adding individual {@link BindingOperation} instances via {@link EvaluationContext#addPreOperation(Operation)}
    */
   public static boolean enqueueSubmission(final Rewrite event, final EvaluationContext context,
            final Parameter<?> parameter, final Object value)
   {
      if (value == null)
         return true;

      List<Operation> operations = new ArrayList<Operation>();
      List<Binding> bindings = parameter.getBindings();
      for (Binding binding : bindings) {
         try {
            if (binding instanceof Evaluation)
            {
               /*
                * Binding to the EvaluationContext is available immediately.
                */
               Object convertedValue = ValueHolderUtil.convert(event, context, parameter.getConverter(), value);
               if (ValueHolderUtil.validates(event, context, parameter.getValidator(), convertedValue))
               {
                  Evaluation evaluation = (Evaluation) binding;
                  evaluation.submit(event, context, value);
                  evaluation.submitConverted(event, context, convertedValue);
               }
               else
                  return false;
            }
            else
            {
               Object convertedValue = ValueHolderUtil.convert(event, context, parameter.getConverter(), value);
               if (ValueHolderUtil.validates(event, context, parameter.getValidator(), convertedValue))
               {
                  operations.add(new BindingOperation(binding, convertedValue));
               }
               else
                  return false;
            }
         }
         catch (Exception e) {
            throw new RewriteException("Failed to bind value [" + value + "] to binding [" + binding + "]", e);
         }
      }

      for (Operation operation : operations) {
         context.addPreOperation(operation);
      }

      return true;
   }

   /**
    * Extract bound values from configured {@link Parameter} instances. Return a {@link List} of the extracted values.
    */
   public static Object performRetrieval(final Rewrite event, final EvaluationContext context,
            final Parameter<?> parameter)
   {
      Object result = null;

      List<Binding> bindings = new ArrayList<Binding>(parameter.getBindings());
      Collections.reverse(bindings);
      // FIXME Should not have to worry about order here.

      for (Binding binding : bindings)
      {
         if (result == null && !(binding instanceof Evaluation) && binding.supportsRetrieval())
         {
            result = binding.retrieve(event, context);
         }
      }

      for (Binding binding : bindings)
      {
         if (binding instanceof Evaluation)
         {
            if (((Evaluation) binding).hasValue(event, context))
            {
               result = binding.retrieve(event, context);
            }
         }
      }

      return result;
   }

   /**
    * Used to store bindings until all conditions have been met.
    *
    * @author Lincoln Baxter, III
    */
   private static class BindingOperation implements Operation
   {
      private final Binding binding;
      private final Object value;

      public BindingOperation(final Binding binding, final Object value)
      {
         this.binding = binding;
         this.value = value;
      }

      @Override
      public void perform(final Rewrite event, final EvaluationContext context)
      {
         binding.submit(event, context, value);
      }

      @Override
      public String toString()
      {
         return "BindingOperation [binding=" + binding + ", value=" + value + "]";
      }
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.ocpsoft.rewrite.prettyfaces;

import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.urlbuilder.Address;
import org.ocpsoft.urlbuilder.AddressBuilder;

import com.ocpsoft.pretty.faces.config.rewrite.RewriteRule;
import com.ocpsoft.pretty.faces.rewrite.RewriteEngine;
import com.ocpsoft.pretty.faces.url.URL;

/**
 * @author Lincoln Baxter, III
 *
 */
public class OutboundRewriteRuleAdaptor implements Rule
{
   private final RewriteRule rule;

   public OutboundRewriteRuleAdaptor(final RewriteRule rule)
   {
      this.rule = rule;
   }

   /**
    * If the given URL is prefixed with this request's context-path, return the URI without the context path. Otherwise
    * return the URI unchanged.
    */
   private String stripContextPath(final String contextPath, String uri)
   {
      if (!contextPath.equals("/") && uri.startsWith(contextPath))
      {
         uri = uri.substring(contextPath.length());
      }
      return uri;
   }

   @Override
   public String getId()
   {
      return toString();
   }

   @Override
   public boolean evaluate(final Rewrite event, final EvaluationContext context)
   {
      if ((event instanceof HttpOutboundServletRewrite) && rule.isOutbound())
      {
         Address outboundResource = ((HttpOutboundServletRewrite) event).getAddress();
         String outboundUrl = outboundResource.toString();

         String contextPath = ((HttpServletRewrite) event).getContextPath();
         if (!contextPath.equals("/") && outboundUrl.startsWith(contextPath))
            outboundUrl = outboundUrl.substring(contextPath.length());

         if (rule.matches(URL.build(outboundUrl).decode().toURL()))
            return true;
      }
      return false;
   }

   @Override
   public void perform(final Rewrite event, final EvaluationContext context)
   {
      RewriteEngine engine = new RewriteEngine();
      HttpOutboundServletRewrite outbound = (HttpOutboundServletRewrite) event;
      String url = outbound.getOutboundAddress().toString();
      String strippedUrl = stripContextPath(outbound.getContextPath(), url);

      String result = "";
      if (!strippedUrl.equals(url))
      {
         result = outbound.getContextPath();
      }
      strippedUrl = engine.processOutbound(((HttpServletRewrite) event).getRequest(),
               ((HttpServletRewrite) event).getResponse(), rule, strippedUrl);
      result += strippedUrl;

      outbound.setOutboundAddress(AddressBuilder.create(result));
   }

}


/*
 * Copyright 2013 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.config;

import java.util.HashSet;
import java.util.Set;

import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.InboundRewrite;
import org.ocpsoft.rewrite.event.OutboundRewrite;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;

/**
 * Utility for creating and wrapping {@link Operation} instances.
 *
 * @author Lincoln Baxter, III
 */
public class Operations
{
   /**
    * Return a new {@link DefaultOperationBuilder} that takes no action when
    * {@link #perform(Rewrite, EvaluationContext)} is invoked.
    */
   public static OperationBuilder create()
   {
      return new NoOp();
   }

   /**
    * Wrap the given {@link Operation} in a new {@link InboundOperation} which will invoke the wrapped operation only
    * for inbound rewrites.
    */
   public static Operation onInbound(final Operation operation)
   {
      return new DefaultOperationBuilderInternal(operation) {

         @Override
         public void perform(Rewrite event, EvaluationContext context)
         {
            if (event instanceof InboundRewrite)
               operation.perform(event, context);
         }

         @Override
         public String toString()
         {
            return "Operations.onInbound(" + operation + ")";
         }
      };
   }

   /**
    * Wrap the given {@link Operation} in a new {@link Operation} which will invoke the wrapped operation only for
    * outbound rewrites.
    */
   public static Operation onOutbound(final Operation operation)
   {
      return new DefaultOperationBuilderInternal(operation) {

         @Override
         public void perform(Rewrite event, EvaluationContext context)
         {
            if (event instanceof OutboundRewrite)
               operation.perform(event, context);
         }

         @Override
         public String toString()
         {
            return "Operations.onOutbound(" + operation + ")";
         }
      };
   }

   /**
    * Wrap a given {@link Operation} as a new {@link DefaultOperationBuilder} that performs the action of the original
    * {@link Operation} when {@link #perform(Rewrite, EvaluationContext)} is invoked.
    */
   public static OperationBuilder wrap(final Operation operation)
   {
      if (operation == null)
         return create();
      if (operation instanceof OperationBuilder)
         return (OperationBuilder) operation;

      return new DefaultOperationBuilderInternal(operation) {
         @Override
         public void perform(Rewrite event, EvaluationContext context)
         {
            operation.perform(event, context);
         }
      };
   }

   /**
    * An operation that is only performed if the current {@link org.ocpsoft.rewrite.event.Rewrite} event is an
    * {@link OutboundRewrite} event.
    *
    * @author Lincoln Baxter, III
    */
   private abstract static class DefaultOperationBuilderInternal extends DefaultOperationBuilder implements
            Parameterized
   {
      private Operation operation;

      public DefaultOperationBuilderInternal(Operation operation)
      {
         this.operation = operation;
      }

      @Override
      public Set<String> getRequiredParameterNames()
      {
         Set<String> result = new HashSet<String>();
         if (operation instanceof Parameterized)
            result.addAll(((Parameterized) operation).getRequiredParameterNames());
         return result;
      }

      @Override
      public void setParameterStore(ParameterStore store)
      {
         if (operation instanceof Parameterized)
            ((Parameterized) operation).setParameterStore(store);
      }
   }

}


/*
 * Copyright 2013 Fabien Marsaudz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.urlbuilder;

/**
 * An {@link Address} with a scheme specific part section.
 *
 * @author Fabien Marsaud
 */
public class AddressBuilderSchemeSpecificPart implements BuildableAddress
{
   private AddressBuilder parent;

   AddressBuilderSchemeSpecificPart(AddressBuilder parent)
   {
      this.parent = parent;
   }

   /**
    * Set a parameter name and value or values. The supplied values will be stored without additional encoding.
    */
   public AddressBuilderSchemeSpecificPart set(CharSequence name, Object... values)
   {
      parent.set(name, values);
      return this;
   }

   /**
    * Set a parameter name and value or values. The values will be decoded before they are stored.
    */
   public AddressBuilderSchemeSpecificPart setDecoded(CharSequence name, Object... values)
   {
      parent.setDecoded(name, values);
      return this;
   }

   /**
    * Set a parameter name and value or values. The values will be encoded before they are stored.
    */
   public AddressBuilderSchemeSpecificPart setEncoded(CharSequence name, Object... values)
   {
      parent.setEncoded(name, values);
      return this;
   }

   @Override
   public Address build()
   {
      return parent.build();
   }

   @Override
   public Address buildLiteral()
   {
      return parent.buildLiteral();
   }

   @Override
   public String toString()
   {
      return parent.toString();
   }

}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.servlet.event;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.ocpsoft.rewrite.AbstractRewrite;
import org.ocpsoft.rewrite.context.Context;
import org.ocpsoft.rewrite.event.Flow;
import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.rewrite.servlet.RewriteLifecycleContext;

/**
 * Base implementation of {@link InboundServletRewriteEvent}
 *
 * @author Lincoln Baxter, III
 */
public abstract class BaseRewrite<IN extends ServletRequest, OUT extends ServletResponse> extends AbstractRewrite
{

   private IN request;
   private OUT response;
   protected Flow flow;
   protected String dispatchResource;
   private Context context;
   private ServletContext servletContext;

   public BaseRewrite(final IN request, final OUT response, final ServletContext servletContext)
   {
      this.servletContext = servletContext;
      flow = ServletRewriteFlow.UN_HANDLED;
      this.request = request;
      this.response = response;
   }

   /*
    * Mutators
    */

   /**
    * Abort the current {@link ServletRequest} immediately. No further action will be taken on, and the request will not
    * be passed to the application.
    */
   public void abort()
   {
      this.flow = ServletRewriteFlow.ABORT_REQUEST;
   }

   /**
    * Continue processing {@link org.ocpsoft.rewrite.config.Rule} definitions for the current {@link ServletRequest},
    * even if a call to {@link #abort()}, {@link #handled()}, {@link #forward(String)}, or any other life-cycle control
    * method has previously been made.
    */
   public void proceed()
   {
      this.flow = ServletRewriteFlow.PROCEED;
   }

   /**
    * Stop processing {@link org.ocpsoft.rewrite.config.Rule} definitions and pass control of the the current
    * {@link ServletRequest} to the underlying application, even if a call to {@link #abort()}, {@link #proceed()},
    * {@link #forward(String)}, or any other life-cycle control method has previously been made.
    */
   public void handled()
   {
      this.flow = ServletRewriteFlow.HANDLED;
   }

   /**
    * Once {@link org.ocpsoft.rewrite.config.Rule} processing has completed, perform a
    * {@link RequestDispatcher#include(ServletRequest, ServletResponse)} of the given resource target.
    */
   public void include(final String resource)
   {
      this.dispatchResource = resource;
      this.flow = ServletRewriteFlow.INCLUDE;
   }

   /**
    * Once {@link org.ocpsoft.rewrite.config.Rule} processing has completed, perform a
    * {@link RequestDispatcher#forward(ServletRequest, ServletResponse)} to the given resource target.
    */
   public void forward(final String resource)
   {
      this.dispatchResource = resource;
      this.flow = ServletRewriteFlow.FORWARD;
   }

   /*
    * Getters
    */
   /**
    * Get the current {@link ServletRewriteFlow} state.
    */
   @Override
   public Flow getFlow()
   {
      return flow;
   }

   /**
    * Set the current {@link ServletRewriteFlow} state.
    */
   public void setFlow(final ServletRewriteFlow flow)
   {
      this.flow = flow;
   }

   /**
    * Get the current dispatch resource. This value is used when performing a {@link #forward(String)},
    * {@link #include(String)}, or redirect.
    */
   public String getDispatchResource()
   {
      return dispatchResource;
   }

   /**
    * Get the current {@link ServletContext} object.
    */
   public ServletContext getServletContext()
   {
      return servletContext;
   }

   /**
    * Get the current {@link ServletRequest} object.
    */
   public IN getRequest()
   {
      return request;
   }

   /**
    * Get the current {@link ServletResponse} object.
    */
   public OUT getResponse()
   {
      return response;
   }

   /**
    * Set the current {@link ServletRequest} object.
    */
   public void setRequest(final IN request)
   {
      this.request = request;
   }

   /**
    * Set the current {@link ServletResponse} object.
    */
   public void setResponse(final OUT response)
   {
      this.response = response;
   }

   /**
    * Enum to represent the finite state of the Rewrite container.
    */
   public enum ServletRewriteFlow implements Flow
   {
      UN_HANDLED(null),
      HANDLED(null),
      PROCEED(UN_HANDLED),

      CONTINUE(HANDLED),

      INCLUDE(HANDLED),

      ABORT_REQUEST(HANDLED),
      FORWARD(ABORT_REQUEST),
      REDIRECT_TEMPORARY(ABORT_REQUEST),
      REDIRECT_PERMANENT(ABORT_REQUEST);

      private ServletRewriteFlow parent;

      private ServletRewriteFlow(final ServletRewriteFlow flow)
      {
         this.parent = flow;
      }

      /**
       * Return true if this {@link ServletRewriteFlow} is a descendant of the given value.
       */
      public boolean is(final ServletRewriteFlow other)
      {
         if (other == null)
         {
            return false;
         }

         ServletRewriteFlow t = this;
         while (t != null)
         {
            if (other == t)
            {
               return true;
            }
            t = t.parent;
         }
         return false;
      }

      /**
       * Return true if the given {@link ServletRewriteFlow} is a descendant of this.
       */
      public boolean contains(final ServletRewriteFlow other)
      {
         if (other == null)
         {
            return false;
         }

         ServletRewriteFlow t = other;
         while (t != null)
         {
            if (this == t)
            {
               return true;
            }
            t = t.parent;
         }
         return false;
      }

      @Override
      public boolean isHandled()
      {
         return is(HANDLED);
      }

      @Override
      public boolean is(Flow flow)
      {
         if (flow instanceof ServletRewriteFlow)
            return is((ServletRewriteFlow) flow);
         return false;
      }
   }

   @Override
   public String toString()
   {
      return "Rewrite [flow=" + flow + ", dispatchResource=" + dispatchResource + "]";
   }

   /**
    * Get the {@link org.ocpsoft.rewrite.context.Context} for the current {@link org.ocpsoft.rewrite.event.Rewrite}.
    */
   @Override
   public Context getRewriteContext()
   {
      if (this.context == null)
      {
         Context context = (Context) request.getAttribute(RewriteLifecycleContext.LIFECYCLE_CONTEXT_KEY);
         if (context == null)
         {
            throw new RewriteException("RewriteContext was null. Something is seriously wrong, " +
                     "or you are attempting to access this event outside of the Rewrite lifecycle.");
         }
         this.context = context;
      }
      return this.context;
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.config;

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

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.common.util.Strings;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;

/**
 * Evaluates all provided {@link Condition} instances. If any conditions return true, this condition
 * returns true. If all provided conditions return false, this condition returns
 * false.
 *
 * @author Lincoln Baxter, III
 */
public final class Or extends DefaultConditionBuilder implements CompositeCondition
{
   private final Condition[] conditions;

   private Or(final Condition... conditions)
   {
      this.conditions = conditions;
   }

   /**
    * Creates a new {@link Or} condition. If any provided conditions return true, this condition returns true.
    *
    * @param conditions the array of conditions to be evaluated
    */
   public static Or any(final Condition... conditions)
   {
      Assert.notNull(conditions, "At least one condition is required.");
      Assert.assertTrue(conditions.length > 0, "At least one condition is required.");
      return new Or(flattenConditions(Arrays.asList(conditions)).toArray(new Condition[] {}));
   }

   private static List<Condition> flattenConditions(List<Condition> conditions)
   {
      List<Condition> result = new ArrayList<Condition>();
      for (Condition condition : conditions) {
         if (condition instanceof Or)
         {
            result.addAll(flattenConditions(((Or) condition).getConditions()));
         }
         else
         {
            result.add(condition);
         }
      }
      return result;
   }

   @Override
   public boolean evaluate(final Rewrite event, final EvaluationContext context)
   {
      boolean result = false;
      for (int i = 0; i < conditions.length; i++) {
         if (conditions[i].evaluate(event, context))
         {
            result = true;
         }
      }
      return result;
   }

   @Override
   public List<Condition> getConditions()
   {
      return Arrays.asList(conditions);
   }

   @Override
   public String toString()
   {
      String result = Strings.join(Arrays.asList(conditions), ".or(");
      for (int i = 0; i < conditions.length; i++) {
         result += ")";
      }
      return result;
   }
}


/*
 * Copyright 2014 Université de Montréal
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.transposition;

import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.rewrite.bind.Binding;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.param.Constraint;
import org.ocpsoft.rewrite.param.Parameter;
import org.ocpsoft.rewrite.param.Parameters;
import org.ocpsoft.rewrite.param.Transposition;

/**
 * A {@link Transposition} and/or {@link Constraint} responsible for translating values from their matching translation
 * from a {@link ResourceBundle}. The lookup in the properties file is case-sensitive. The properties file should use
 * ISO-8859-1 encoding as defined by {@link PropertyResourceBundle}.
 *
 * Requires inverted properties files in the form of translated_name=name_to_bind.
 *
 * TODO Should we allow to load the properties file in a different encoding? TODO Allow to enable case insensitive
 * and/or ascii-folding on property lookup?
 *
 * @author Christian Gendreau
 */
public class LocaleTransposition implements Transposition<String>, Constraint<String>
{
   // shared thread safe map between a String(representing the language) and a ResourceBundle.
   private static Map<String, ResourceBundle> bundleMap = new ConcurrentHashMap<String, ResourceBundle>();

   private final String languageParam;
   private final String bundleName;

   private Operation onFailureOperation;

   private LocaleTransposition(final String languageParam, final String bundleName)
   {
      Assert.notNull(languageParam, "Language must not be null.");
      Assert.notNull(bundleName, "Bundle must not be null.");

      // TODO ensure that we can find a resource bundle with the provided name in the classpath

      this.languageParam = languageParam;
      this.bundleName = bundleName;

   }

   /**
    * Translate a value into the matching one from a resource bundle in specified language.
    *
    * @param lang
    * @param value
    * @return translated value or null if no bundle can be found for the specified language or no entries in the bundle
    *         match the given value.
    */
   private String translate(String lang, String value)
   {
      String translatation = null;
      if (value != null)
      {
         if (!bundleMap.containsKey(lang))
         {
            Locale locale = new Locale(lang);
            try
            {
               ResourceBundle loadedBundle = ResourceBundle.getBundle(bundleName, locale,
                        ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT));

               bundleMap.put(lang, loadedBundle);
            }
            catch (MissingResourceException e)
            {
               return null;
            }
         }

         try
         {
            // can we received more than one path section? e.g./search/service/
            translatation = bundleMap.get(lang).getString(value);
         }
         catch (MissingResourceException mrEx)
         {
            // ignore
         }
      }
      return translatation;
   }

   /**
    * Create a {@link Binding} to a {@link ResourceBundle}, where the initial bound {@link Parameter} value is used as
    * the bundle lookup key. The resultant value of the bundle lookup is stored as the new bound {@link Parameter}
    * value.
    *

    * For example, consider the following URL-based rule:
    *
    *

    * Configuration config = ConfigurationBuilder.begin()
    *          .addRule(Join.path("/{lang}/{path}").to("/{path}"))
    *          .where("path").transposedBy(LocaleTransposition.bundle("org.example.Paths", "lang"));
    *

    *

    * In the above scenario, "org.example.Paths" is the resource bundle name. The value of the {@link Parameter} "lang"
    * is extracted from the inbound request URL, and used as the bundle {@link Locale}. The initial value of the
    * {@link Parameter} "path" is used as the lookup key, and is transposed to the value of the corresponding resource
    * bundle entry. Once transposition has occurred, after rule evaluation, subsequent references to the "path"
    * {@link Parameter} will return the value from the {@link ResourceBundle} entry.
    *

    * When this example is applied to a URL of: "/de/bibliotek", assuming a bundle called "org.example.Paths_de" exists
    * and contains the an entry "bibliotek=library", the rule will forward to the new URL: "/library", because the value
    * of "path" has been transposed by {@link LocaleTransposition}.
    *
    *
    * @param bundleName Fully qualified name of the {@link ResourceBundle}
    * @param localeParam The name of the {@link Parameter} that contains the ISO 639-1 language code to be used with
    *           {@link Locale}.
    *
    * @return new instance of LocaleBinding
    */
   public static LocaleTransposition bundle(final String bundleName, final String localeParam)
   {
      return new LocaleTransposition(localeParam, bundleName);
   }

   /**
    * Specify an {@link Operation} to be added as a preOperation in case the {@link Transposition} failed. Failure
    * occurs when no {@link ResourceBundle} can be found for the requested language or when a value can not be
    * transposed due to a missing key in the resource bundle.
    *
    * @param onFailureOperation
    * @return
    */
   public LocaleTransposition onTranspositionFailed(Operation onFailureOperation)
   {
      this.onFailureOperation = onFailureOperation;
      return this;
   }

   @Override
   public String transpose(Rewrite event, EvaluationContext context, String value)
   {
      // Retrieve the value of lang from the context
      String targetLang = (String) Parameters.retrieve(context, this.languageParam);
      String transposedValue = translate(targetLang, value);

      if (transposedValue == null)
      {
         if (onFailureOperation != null)
         {
            context.addPreOperation(onFailureOperation);
         }
         // if language is not defined, do not translate and keep original value.
         transposedValue = value;
      }
      return transposedValue;
   }

   @Override
   public boolean isSatisfiedBy(Rewrite event, EvaluationContext context, String value)
   {
      // Retrieve the value of lang from the context
      String targetLang = (String) Parameters.retrieve(context, this.languageParam);
      String translation = translate(targetLang, value);

      return (translation != null);
   }
}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocpsoft.pretty.faces.el;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * An implementation of {@link PrettyExpression} used when the name of the bean
 * should be resolved lazily. This class is used by the annotation configuration
 * mechanism.
 *
 * @author Christian Kaltepoth
 */
public class LazyExpression implements PrettyExpression
{

   private final static Log log = LogFactory.getLog(LazyExpression.class);

   /**
    * LazyBeanNameFinder used to resolve the bean names
    */
   private final LazyBeanNameFinder finder;

   /**
    * the bean class
    */
   private final Class<?> beanClass;

   /**
    * The component of the bean the expression refers to. Can be a single
    * property name, a property path or method name.
    */
   private final String component;

   /**
    * Holds the expression once it has been build lazily
    */
   private String expression;

   /**
    * Creates a new {@link LazyExpression}
    *
    * @param finder
    *            The bean name finder to user for lazy resolving
    * @param beanClass
    *            the class of the bean
    * @param component
    *            the component of the bean referenced by the expression. Can be
    *            a single property name, a property path or a method name
    */
   public LazyExpression(LazyBeanNameFinder finder, Class<?> beanClass, String component)
   {
      this.finder = finder;
      this.beanClass = beanClass;
      this.component = component;
   }

   /*
    * @see com.ocpsoft.pretty.faces.expression.PrettyExpression#getELExpression()
    */
   public String getELExpression()
   {

      // build the expression if not already done
      if (expression == null)
      {

         /*
          * Build the expression. Note that findBeanName() will either
          * return the resolved bean name or throw a runtime exception
          */
         expression = "#{" + finder.findBeanName(beanClass) + "." + component + "}";

         // log the resolved expression on trace level
         if (log.isTraceEnabled())
         {
            log.trace("Lazy expression resolved to: " + expression);
         }

      }
      return expression;
   }

   @Override
   public String toString()
   {
      return "#{[" + beanClass.getName() + "]." + component + "}";
   }

   /**
    * The class this lazy expression refers to.
    *
    * @return The class of the bean
    */
   public Class<?> getBeanClass()
   {
      return beanClass;
   }

   /**
    * The component of the bean the expression refers to.
    *
    * @return the component of the bean referenced by the expression. Can be a
    *         single property name, a property path or a method name
    */
   public String getComponent()
   {
      return component;
   }

   /*
    * @see java.lang.Object#hashCode()
    */
   @Override
   public int hashCode()
   {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((beanClass == null) ? 0 : beanClass.hashCode());
      result = prime * result + ((expression == null) ? 0 : expression.hashCode());
      return result;
   }

   /*
    * @see java.lang.Object#equals(java.lang.Object)
    */
   @Override
   public boolean equals(Object obj)
   {
      if (this == obj)
      {
         return true;
      }
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      LazyExpression other = (LazyExpression) obj;
      if (beanClass == null)
      {
         if (other.beanClass != null)
         {
            return false;
         }
      }
      else if (!beanClass.equals(other.beanClass))
      {
         return false;
      }
      if (expression == null)
      {
         if (other.expression != null)
         {
            return false;
         }
      }
      else if (!expression.equals(other.expression))
      {
         return false;
      }
      return true;
   }

}


/*
 * Copyright 2013 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.param;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.common.util.Assert;
import org.ocpsoft.common.util.Iterators;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.spi.GlobalParameterProvider;
import org.ocpsoft.rewrite.util.ServiceLogger;

/**
 * Default implementation of {@link ParameterValueStore}
 *
 * @author Lincoln Baxter, III
 */
public class DefaultParameterValueStore implements ParameterValueStore, Iterable<Entry<Parameter<?>, String>>
{
   Map<Parameter<?>, String> map = new LinkedHashMap<Parameter<?>, String>();
   private static List<GlobalParameterProvider> providers;
   private static final Logger log = Logger.getLogger(DefaultParameterValueStore.class);

   /**
    * Create a new, empty {@link DefaultParameterValueStore} instance.
    */
   @SuppressWarnings("unchecked")
   public DefaultParameterValueStore()
   {
      if (providers == null)
      {
         providers = Iterators.asList(ServiceLoader.load(GlobalParameterProvider.class));
         ServiceLogger.logLoadedServices(log, GlobalParameterProvider.class, providers);
      }
   }

   /**
    * Create a new {@link DefaultParameterValueStore} instance, copying all {@link Parameter} and value pairs from the
    * given instance.
    */
   public DefaultParameterValueStore(DefaultParameterValueStore instance)
   {
      for (Entry<Parameter<?>, String> entry : instance)
      {
         map.put(entry.getKey(), entry.getValue());
      }
   }

   @Override
   public String retrieve(Parameter<?> parameter)
   {
      String value = map.get(parameter);
      return value;
   }

   @Override
   public boolean submit(Rewrite event, EvaluationContext context, Parameter<?> param, String value)
   {
      Assert.notNull(event, "Rewrite event must not be null.");
      Assert.notNull(context, "EvaluationContext must not be null.");
      Assert.notNull(param, "Parameter must not be null.");

      boolean result = false;
      boolean supportsSubmission = supportsSubmission(event, context, param, value);
      if (!supportsSubmission)
      {
         result = true;
      }
      else if (supportsSubmission && isValid(event, context, param, value))
      {
         // FIXME Transposition processing will break multi-conditional matching
         for (Transposition<String> transposition : param.getTranspositions())
         {
            value = transposition.transpose(event, context, value);
         }
         map.put(param, value);
         result = true;
      }

      return result;
   }

   private boolean supportsSubmission(Rewrite event, EvaluationContext context, Parameter<?> param, String value)
   {
      boolean result = true;
      for (GlobalParameterProvider provider : providers)
      {
         Set<Parameter<?>> params = provider.getParameters();
         if (params != null)
         {
            for (Parameter<?> parameter : params)
            {
               if (parameter != null && parameter.getName() != null && parameter.getName().equals(param.getName()))
               {
                  result = provider.supportsSubmission(event, context, parameter);
                  break;
               }
            }
         }
      }

      return result;
   }

   @Override
   public boolean isValid(Rewrite event, EvaluationContext context, Parameter<?> param, String value)
   {
      Assert.notNull(event, "Rewrite event must not be null.");
      Assert.notNull(context, "EvaluationContext must not be null.");
      Assert.notNull(param, "Parameter must not be null.");

      String stored = map.get(param);
      boolean result = false;
      if (_doParameterProviderValidation(event, context, param, value))
      {
         result = true;
      }
      else if (stored == value || (stored != null && stored.equals(value)))
      {
         result = true;
      }
      else if (stored == null)
      {
         result = true;
         for (Constraint<String> constraint : param.getConstraints())
         {
            if (!constraint.isSatisfiedBy(event, context, value))
            {
               result = false;
            }
         }
      }

      return result;
   }

   private boolean _doParameterProviderValidation(Rewrite event, EvaluationContext context, Parameter<?> param,
            String value)
   {
      boolean result = false;
      for (GlobalParameterProvider provider : providers)
      {
         Set<Parameter<?>> params = provider.getParameters();
         if (params != null)
         {
            for (Parameter<?> parameter : params)
            {
               if (parameter != null && parameter.getName() != null && parameter.getName().equals(param.getName()))
               {
                  result = provider.isValid(event, context, param, value);
                  break;
               }
            }
         }
      }
      return result;
   }

   @Override
   public Iterator<Entry<Parameter<?>, String>> iterator()
   {
      return map.entrySet().iterator();
   }

   @Override
   public String toString()
   {
      return map.keySet().toString();
   }

   /**
    * Retrieve the current {@link ParameterValueStore} from the given {@link EvaluationContext} instance.
    *
    * @throws IllegalStateException If the {@link ParameterValueStore} could not be located.
    */
   public static ParameterValueStore getInstance(EvaluationContext context) throws IllegalStateException
   {
      ParameterValueStore valueStore = (ParameterValueStore) context.get(ParameterValueStore.class);
      if (valueStore == null)
      {
         throw new IllegalStateException("Could not retrieve " + ParameterValueStore.class.getName() + " from "
                  + EvaluationContext.class.getName() + ". Has the " + EvaluationContext.class.getSimpleName()
                  + " been set up properly?");
      }
      return valueStore;
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.servlet.config;

import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedPattern;
import org.ocpsoft.rewrite.param.ParameterizedPatternParser;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;

/**
 * A {@link Condition} responsible for asserting on specified values of {@link HttpServletRequest#getHeader(String)}.
 *
 * @author Lincoln Baxter, III
 */
public abstract class Header extends HttpCondition implements Parameterized
{
   private final ParameterizedPatternParser name;
   private final ParameterizedPatternParser value;

   private Header(final String name, final String value)
   {
      Assert.notNull(name, "Header name pattern cannot be null.");
      Assert.notNull(value, "Header value pattern cannot be null.");
      this.name = new RegexParameterizedPatternParser(name);
      this.value = new RegexParameterizedPatternParser(value);
   }

   /**
    * Create a {@link Header} condition that matches against both header name and values.
    *

    * The given name and value patterns may be parameterized:
    *

    *
    *    Header.matches("Accept-Encoding", "{encoding}")

    *    Header.matches("Accept-{type}", "{value}")

    *    ...
    *
    *

    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String) {@link HttpServletRequest#getHeader(String)}
    *
    * @param name {@link ParameterizedPattern} matching the header name.
    * @param value {@link ParameterizedPattern} matching the header value.
    */
   public static Header matches(final String name, final String value)
   {
      return new Header(name, value) {
         @Override
         public String toString()
         {
            return "Header.matches(\"" + name + "\", \"" + value + "\")";
         }
      };
   }

   /**
    * Create a {@link Header} condition that matches only against the existence of a header with a name matching the
    * given pattern. The header value is ignored.
    *

    * The given name pattern may be parameterized:
    *

    *
    *    Header.exists("Accept-Encoding")

    *    Header.exists("Accept-{acceptType}")

    *    Header.exists("{anything}")

    *    ...
    *
    *

    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String) {@link HttpServletRequest#getHeader(String)}
    *
    * @param name {@link ParameterizedPattern} matching the header name.
    */
   public static Header exists(final String name)
   {
      return new Header(name, "{" + Header.class.getName() + "_value}") {
         @Override
         public String toString()
         {
            return "Header.exists(\"" + name + "\")";
         }
      };
   }

   /**
    * Create a {@link Header} condition that matches only against the existence of a header with value matching the
    * given pattern. The header name is ignored.
    *

    * The given value pattern may be parameterized:
    *

    *
    *    Header.valueExists("application/xml")

    *    Header.valueExists("application/{type}")

    *    Header.valueExists("{anything}")

    *    ...
    *
    *

    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String) {@link HttpServletRequest#getHeader(String)}
    *
    * @param value {@link ParameterizedPattern} matching the header value.
    */
   public static Header valueExists(final String value)
   {
      return new Header("{" + Header.class.getName() + "_name}", value) {
         @Override
         public String toString()
         {
            return "Header.valueExists(\"" + value + "\")";
         }
      };
   }

   @Override
   @SuppressWarnings("unchecked")
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      HttpServletRequest request = event.getRequest();
      Enumeration<String> headerNames = request.getHeaderNames();
      if (headerNames != null)
      {
         for (String header : Collections.list(headerNames))
         {
            if (name.parse(header).submit(event, context) && matchesValue(event, context, request, header))
            {
               return true;
            }
         }
      }
      return false;
   }

   @SuppressWarnings("unchecked")
   private boolean matchesValue(Rewrite event, EvaluationContext context, final HttpServletRequest request,
            final String header)
   {
      Enumeration<String> headers = request.getHeaders(header);
      if (headers != null)
      {
         for (String contents : Collections.list(headers))
         {
            if (value.parse(contents).submit(event, context))
            {
               return true;
            }
         }
      }
      return false;
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      Set<String> result = new LinkedHashSet<String>();
      result.addAll(name.getRequiredParameterNames());
      result.addAll(value.getRequiredParameterNames());
      return result;
   }

   @Override
   public void setParameterStore(ParameterStore store)
   {
      name.setParameterStore(store);
      value.setParameterStore(store);
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ocpsoft.rewrite.config;

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

import org.ocpsoft.common.util.Strings;
import org.ocpsoft.rewrite.context.Context;

/**
 * A fluent builder for defining {@link Configuration} objects.
 *
 * @author Lincoln Baxter, III
 */
public class ConfigurationBuilder implements Configuration, ConfigurationBuilderRoot
{
   private final List<RuleBuilder> rules = new ArrayList<RuleBuilder>();

   ConfigurationBuilder()
   {}

   @Override
   public List<Rule> getRules()
   {
      return Collections.<Rule> unmodifiableList(rules);
   }

   public List<RuleBuilder> getRuleBuilders()
   {
      return Collections.unmodifiableList(rules);
   }

   /**
    * Begin defining a new fluent {@link Configuration}.
    */
   public static ConfigurationBuilder begin()
   {
      return new ConfigurationBuilder();
   }

   /**
    * Add a new fluent {@link Rule}.
    */
   @Override
   public ConfigurationRuleBuilderCustom addRule()
   {
      RuleBuilder rule = RuleBuilder.define();
      setDefaultLocation(rule);
      rules.add(rule);
      return new ConfigurationRuleBuilder(this, rule);
   }

   ConfigurationRuleBuilderCustom addOtherwiseRule(RuleBuilder origin)
   {
      RuleBuilder rule = RuleBuilder.define();

      /**
       * .otherwise() clauses should create a Rule that shares state with its origin.
       */
      rule.contextMap = origin.contextMap;
      rules.add(rule);
      return new ConfigurationRuleBuilder(this, rule);
   }

   /**
    * Add a {@link Rule}.
    */
   @Override
   public ConfigurationRuleBuilder addRule(Rule rule)
   {
      RuleBuilder wrapped = null;
      if (rule instanceof RuleBuilder)
         wrapped = (RuleBuilder) rule;
      else
         wrapped = RuleBuilder.wrap(rule);

      rules.add(wrapped);
      setDefaultLocation(wrapped);
      return new ConfigurationRuleBuilder(this, wrapped);
   }

   private void setDefaultLocation(Rule rule)
   {
      if (rule instanceof Context)
      {
         Throwable throwable = new Throwable().fillInStackTrace();
         StackTraceElement[] trace = throwable.getStackTrace();

         StackTraceElement location = null;
         for (StackTraceElement element : trace) {
            if (!element.getClassName().startsWith("org.ocpsoft.rewrite.config"))
            {
               location = element;
               break;
            }
         }

         if (location != null && ((Context) rule).get(RuleMetadata.PROVIDER_LOCATION) == null)
         {
            ((Context) rule).put(RuleMetadata.PROVIDER_LOCATION, location.toString());
         }
      }
   }

   @Override
   public String toString()
   {
      return "ConfigurationBuilder.begin()\n" + Strings.join(rules, "\n");
   }

}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocpsoft.pretty.faces.util;

import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;

/**
 * @author lb3
 */
public class FacesStateUtils
{
   /**
    * Determine if the current request/FacesContext is in PostBack state
    */
   public static boolean isPostback(final FacesContext context)
   {
      RenderKitFactory factory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
      RenderKit renderKit = factory.getRenderKit(context, RenderKitFactory.HTML_BASIC_RENDER_KIT);
      return renderKit.getResponseStateManager().isPostback(context);
   }
}