package org.ocpsoft.rewrite.servlet.config;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedPattern;
import org.ocpsoft.rewrite.param.ParameterizedPatternBuilder;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternBuilder;
import org.ocpsoft.rewrite.servlet.config.response.ResponseStreamWrapper;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.util.Transpositions;

/**
 * An {@link Operation} responsible for streaming a {@link File} on the host file-system to the
 * {@link HttpServletResponse#getOutputStream()}.
 *
 * @author Lincoln Baxter, III
 */
public abstract class Stream extends HttpOperation implements Parameterized
{
   public static final Logger log = Logger.getLogger(Stream.class);

   // TODO TEST ME!!!
   public final static String STREAM_KEY = Stream.class.getName() + "_STREAM";

   protected RegexParameterizedPatternBuilder target;

   private Stream(File target)
   {
      this.target = new RegexParameterizedPatternBuilder(target.getAbsolutePath());
   }

   /**
    * Create an {@link Operation} that streams the given {@link File} to the
    * {@link HttpServletResponse#getOutputStream()}.
    *
    *

    * The given {@link File} path may be parameterized:
    *

    *
    *    new File("/tmp/file.txt")

    *    new File("c:\tmp\{param}.txt")

    *    ...
    *
    *

    *
    * @param location {@link ParameterizedPattern} specifying the location of the {@link File}.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static Stream from(File file)
   {
      return new Stream(file) {

         @Override
         public void performHttp(HttpServletRewrite event, EvaluationContext context)
         {
            InputStream stream = null;
            try {
               File file = new File(target.build(event, context, Transpositions.encodePath()));
               stream = new BufferedInputStream(new FileInputStream(file));
               log.debug("Streaming from file [" + file + "] to response.");
               Response.write(stream).perform(event, context);
            }
            catch (Exception e) {
               throw new RewriteException("Error streaming file.", e);
            }
            finally
            {
               if (stream != null)
                  try {
                     stream.close();
                  }
                  catch (IOException e) {
                     throw new RewriteException("Error closing stream.", e);
                  }
            }
         }

         @Override
         public String toString()
         {
            return "Stream.from(\"" + target.getPattern() + "\")";
         }
      };
   }

   /**
    * Create an {@link Operation} that duplicates content written to {@link HttpServletResponse#getOutputStream()} and
    * writes it to the given {@link File}.
    */
   public static Operation to(File file)
   {
      return new Stream(file) {
         @Override
         public void performHttp(HttpServletRewrite event, EvaluationContext context)
         {
            final File file = new File(target.build(event, context, Transpositions.encodePath()));
            if (!file.exists())
            {
               file.mkdirs();
               file.delete();
               try {
                  file.createNewFile();
               }
               catch (IOException e) {
                  throw new RewriteException("Could not create file for Stream operation", e);
               }
            }

            Response.withOutputStreamWrappedBy(new ResponseStreamWrapper() {

               @Override
               public OutputStream wrap(HttpServletRewrite rewrite, OutputStream outputStream)
               {
                  try {
                     BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
                     rewrite.getRequest().setAttribute(STREAM_KEY, stream);
                     log.debug("Cloning response OutputStream to file [" + file + "]");
                     return new MultiOutputStream(stream, outputStream);
                  }
                  catch (FileNotFoundException e) {
                     throw new RewriteException("Could not wrap stream", e);
                  }
               }

               @Override
               public void finish(HttpServletRewrite rewrite)
               {
                  try {
                     OutputStream stream = (OutputStream) rewrite.getRequest().getAttribute(STREAM_KEY);
                     if (stream != null)
                     {
                        log.debug("Closing cloned file [" + file + "] OutputStream");
                        stream.flush();
                        stream.close();
                     }

                  }
                  catch (Exception e) {
                     throw new RewriteException("Could not close stream", e);
                  }
               }
            }).perform(event, context);
         }

         @Override
         public String toString()
         {
            return "Stream.to(\"" + target.getPattern() + "\")";
         }
      };
   }

   private static class MultiOutputStream extends OutputStream
   {
      private OutputStream[] streams;

      public MultiOutputStream(OutputStream... streams)
      {
         this.streams = streams;
      }

      @Override
      public void close() throws IOException
      {
         for (int i = 0; i < streams.length; i++)
            streams[i].close();
      }

      @Override
      public void flush() throws IOException
      {
         for (int i = 0; i < streams.length; i++)
            streams[i].flush();
      }

      @Override
      public void write(int b) throws IOException
      {
         for (int i = 0; i < streams.length; i++)
            streams[i].write(b);
      }

      @Override
      public void write(byte[] b) throws IOException
      {
         for (int i = 0; i < streams.length; i++)
            streams[i].write(b);
      }

      @Override
      public void write(byte[] b, int off, int len) throws IOException
      {
         for (int i = 0; i < streams.length; i++)
            streams[i].write(b, off, len);
      }
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      return target.getRequiredParameterNames();
   }

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

   public ParameterizedPatternBuilder getExpression()
   {
      return target;
   }

}


/*
 * 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.el;

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.common.services.ServiceLoader;
import org.ocpsoft.common.util.Iterators;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.bind.Binding;
import org.ocpsoft.rewrite.bind.Retrieval;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.el.spi.ExpressionLanguageProvider;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.exception.RewriteException;

/**
 * Responsible for binding to EL expressions.
 *
 * @author Lincoln Baxter, III
 */
public abstract class El implements Binding, Retrieval
{
   private static final Logger log = Logger.getLogger(El.class);
   private static volatile List<ExpressionLanguageProvider> _providers;

   /**
    * Create a new EL Method binding using distinct expressions to submit and retrieve values. The method intended for
    * use in submission must accept a single parameter of the expected type.
    */
   public static El method(final String retrieve, final String submit)
   {
      return new ElMethod(new ConstantExpression(retrieve), new ConstantExpression(submit));
   }

   /**
    * Create a new EL Method binding to retrieve values. The method must return a value of the expected type.
    */
   public static El retrievalMethod(final String expression)
   {
      return new ElMethod(new ConstantExpression(expression), null);
   }

   /**
    * Create a new EL Method binding to retrieve values. This method allows the caller to supply {@link Method} instance
    * to refer to the method that should be invoked.
    */
   public static El retrievalMethod(final Method method)
   {
      return retrievalMethod(method.getDeclaringClass(), method.getName());
   }

   /**
    * Create a new EL Method binding to retrieve values. This method allows the caller to supply the {@link Class}
    * representing the type of the target object and the method name to refer to the method that should be invoked.
    */
   public static El retrievalMethod(final Class<?> clazz, final String methodName)
   {
      return new ElMethod(new TypeBasedExpression(clazz, methodName), null);
   }

   /**
    * Create a new EL Method binding to submit values. The method must accept a single parameter of the expected type.
    */
   public static El submissionMethod(final String expression)
   {
      return new ElMethod(null, new ConstantExpression(expression));
   }

   /**
    * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must
    * either be public, or have a publicly defined getter/setter.
    */
   public static El property(final String expression)
   {
      return new ElProperty(new ConstantExpression(expression));
   }

   /**
    * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must
    * either be public, or have a publicly defined getter/setter. Instead of an EL expression this method expects a
    * {@link Field} argument. The EL expression will be automatically created at runtime.
    */
   public static El property(final Field field)
   {
      return property(field.getDeclaringClass(), field.getName());
   }

   /**
    * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must
    * either be public, or have a publicly defined getter/setter. Instead of an EL expression this method expects the
    * {@link Class} representing the type of the target object and the field name. The EL expression will be
    * automatically created at runtime.
    */
   public static El property(final Class<?> clazz, final String fieldName)
   {
      return new ElProperty(new TypeBasedExpression(clazz, fieldName));
   }

   /**
    * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must
    * either be public, or have a publicly defined getter/setter.
    */
   public static El properties(final String submit, final String retrieve)
   {
      return new ElProperties(new ConstantExpression(submit),
               new ConstantExpression(retrieve));
   }

   /**
    * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must
    * either be public, or have a publicly defined getter/setter. Instead of an EL expression this method expects a
    * {@link Field} argument. The EL expression will be automatically created at runtime.
    */
   public static El properties(final Field submit, final Field retrieve)
   {
      return new ElProperties(new TypeBasedExpression(submit.getDeclaringClass(), submit.getName()),
               new TypeBasedExpression(retrieve.getDeclaringClass(), retrieve.getName()));
   }

   @SuppressWarnings("unchecked")
   private static List<ExpressionLanguageProvider> getProviders()
   {
      if (_providers == null)
      {
         synchronized (El.class)
         {
            if (_providers == null)
            {
               _providers = Iterators.asList(ServiceLoader.load(ExpressionLanguageProvider.class));
               Collections.sort(_providers, new WeightedComparator());

               if (_providers.isEmpty())
               {
                  log.warn("No instances of [{}] were configured. EL support is disabled.",
                           ExpressionLanguageProvider.class.getName());
               }

            }

         }
      }
      return _providers;
   }

   private static Object executeProviderCallable(Rewrite event, EvaluationContext context,
            ProviderCallable<Object> providerCallable)
   {
      List<Exception> exceptions = new ArrayList<Exception>();
      for (ExpressionLanguageProvider provider : getProviders()) {
         try
         {
            return providerCallable.call(event, context, provider);
         }
         catch (RuntimeException e) {
            throw e;
         }
         catch (Exception e)
         {
            exceptions.add(e);
         }
      }

      for (Exception exception : exceptions) {
         log.error("DEFERRED EXCEPTION", exception);
      }
      throw new RewriteException("No registered " + ExpressionLanguageProvider.class.getName()
               + " could handle the Expression [" + providerCallable.getExpression() + "]");
   }

   /**
    * Handle EL Method Invocation and Value Extraction
    */
   public static class ElMethod extends El
   {
      private final Expression getExpression;
      private final Expression setExpression;

      public ElMethod(final Expression getExpression, final Expression setExpression)
      {
         this.getExpression = getExpression;
         this.setExpression = setExpression;
      }

      @Override
      public Object retrieve(final Rewrite event, final EvaluationContext context)
      {
         if (!supportsRetrieval())
            throw new RewriteException("Method binding expression supports submission only [" + setExpression
                     + "], no value retrieval expression was defined");

         return executeProviderCallable(event, context, new ProviderCallable<Object>() {
            @Override
            public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider)
                     throws Exception
            {
               return provider.evaluateMethodExpression(getExpression.getExpression());
            }

            @Override
            public String getExpression()
            {
               return ElMethod.this.getExpression.getExpression();
            }
         });
      }

      @Override
      public Object submit(final Rewrite event, final EvaluationContext context, final Object value)
      {
         if (!supportsSubmission())
            throw new RewriteException("Method binding expression supports retrieval only [" + getExpression
                     + "], no value submission expression was defined");

         return executeProviderCallable(event, context, new ProviderCallable<Object>() {
            @Override
            public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider)
                     throws Exception
            {
               return provider.evaluateMethodExpression(setExpression.getExpression(), value);
            }

            @Override
            public String getExpression()
            {
               return ElMethod.this.setExpression.getExpression();
            }
         });
      }

      @Override
      public boolean supportsRetrieval()
      {
         return getExpression != null;
      }

      @Override
      public boolean supportsSubmission()
      {
         return setExpression != null;
      }

      @Override
      public String toString()
      {
         return "ElMethod [retrieve= [ " + getExpression + " }, submit= [ " + setExpression + " ]";
      }

   }

   /**
    * Handle EL Property Injection and Extraction
    */
   public static class ElProperty extends El
   {
      private final Expression expression;

      public ElProperty(final Expression expression)
      {
         this.expression = expression;
      }

      @Override
      public Object retrieve(final Rewrite event, final EvaluationContext context)
      {
         return executeProviderCallable(event, context, new ProviderCallable<Object>() {
            @Override
            public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider)
                     throws Exception
            {
               return provider.retrieveValue(expression.getExpression());
            }

            @Override
            public String getExpression()
            {
               return ElProperty.this.expression.getExpression();
            }
         });
      }

      @Override
      public Object submit(final Rewrite event, final EvaluationContext context, final Object value)
      {
         return executeProviderCallable(event, context, new ProviderCallable<Object>() {
            @Override
            public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider)
                     throws Exception
            {
               provider.submitValue(expression.getExpression(), value);
               return null;
            }

            @Override
            public String getExpression()
            {
               return ElProperty.this.expression.getExpression();
            }
         });
      }

      @Override
      public boolean supportsRetrieval()
      {
         return true;
      }

      @Override
      public boolean supportsSubmission()
      {
         return true;
      }

      @Override
      public String toString()
      {
         return "ElProperty [ " + expression + " ]";
      }

   }

   public static class ElProperties extends El
   {

      private Expression submit;
      private Expression retrieve;

      public ElProperties(final Expression submit, final Expression retrieve)
      {
         this.submit = submit;
         this.retrieve = retrieve;
      }

      @Override
      public Object retrieve(final Rewrite event, final EvaluationContext context)
      {
         return executeProviderCallable(event, context, new ProviderCallable<Object>() {
            @Override
            public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider)
                     throws Exception
            {
               return provider.retrieveValue(retrieve.getExpression());
            }

            @Override
            public String getExpression()
            {
               return retrieve.getExpression();
            }
         });
      }

      @Override
      public Object submit(final Rewrite event, final EvaluationContext context, final Object value)
      {
         return executeProviderCallable(event, context, new ProviderCallable<Object>() {
            @Override
            public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider)
                     throws Exception
            {
               provider.submitValue(submit.getExpression(), value);
               return null;
            }

            @Override
            public String getExpression()
            {
               return submit.getExpression();
            }
         });
      }

      @Override
      public boolean supportsRetrieval()
      {
         return true;
      }

      @Override
      public boolean supportsSubmission()
      {
         return true;
      }

      @Override
      public String toString()
      {
         return "ElProperties [ submitTo =>" + submit + ", retrieveFrom=>" + retrieve + " ]";
      }

   }
}


/*
 * 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.Map.Entry;
import java.util.Set;

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.InboundRewrite;
import org.ocpsoft.rewrite.param.Parameter;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.ParameterValueStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedPattern;
import org.ocpsoft.rewrite.param.ParameterizedPatternParser;
import org.ocpsoft.rewrite.param.ParameterizedPatternResult;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.util.QueryStringBuilder;
import org.ocpsoft.urlbuilder.Address;

/**
 * A {@link Condition} that inspects the value of {@link HttpServletRewrite#getRequestQueryString()}
 *
 * @author Lincoln Baxter, III
 */
@SuppressWarnings("deprecation")
public abstract class Query extends HttpCondition implements Parameterized
{
   /**
    * Create a {@link Condition} matching the current {@link Address#getQuery()}.
    *

    * The given pattern may be parameterized:
    *

    *
    *    ?param=value

    *    ?param={param}

    *    ?param={param}&foo=bar

    *    ...
    *
    *

    *
    * @param pattern {@link ParameterizedPattern} matching the query string of the current {@link Address}.
    *
    * @see ConfigurationRuleParameterBuilder#where(String)
    */
   public static Query matches(final String query)
   {
      Assert.notNull(query, "URL pattern must not be null.");

      return new Query() {
         private ParameterStore store;
         final ParameterizedPatternParser pattern = new RegexParameterizedPatternParser(query);

         @Override
         public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
         {
            String queryString = null;
            if (event instanceof InboundRewrite)
            {
               queryString = event.getInboundAddress().getQuery();
            }
            else if (event instanceof HttpOutboundServletRewrite)
            {
               queryString = ((HttpOutboundServletRewrite) event).getOutboundAddress().getQuery();
            }

            if (pattern.parse(queryString == null ? "" : queryString).submit(event, context))
            {
               ParameterValueStore values = (ParameterValueStore) context.get(ParameterValueStore.class);
               for (Entry<Parameter<?>, String> entry : pattern.parse(query).getParameters(context).entrySet()) {
                  values.submit(event, context, store.get(entry.getKey().getName()), entry.getValue());
               }
               return true;
            }
            return false;
         }

         @Override
         public String toString()
         {
            return "Query.matches(\"" + query + "\")";
         }

         @Override
         public Set<String> getRequiredParameterNames()
         {
            return pattern.getRequiredParameterNames();
         }

         @Override
         public void setParameterStore(ParameterStore store)
         {
            pattern.setParameterStore(store);
            this.store = store;
         }
      };
   }

   /**
    * Create a {@link Condition} asserting the existence of specific parameter names within the current
    * {@link Address#getQuery()}
    *

    * The given parameter name is automatically parameterized:
    *

    *
    *    Query.paramterExists("param")

    *    ...

    *    Forward.to("/{param}/page.jsp")
    *
    *

    *
    * @param pattern name of the {@link Parameter} matching query parameter names within the current
    *           {@link Address#getQuery()}.
    *
    * @see ConfigurationRuleParameterBuilder#where(String)
    */
   public static Query parameterExists(final String name)
   {
      Assert.notNull(name, "Parameter name pattern must not be null.");

      return new Query() {
         final ParameterizedPatternParser pattern = new RegexParameterizedPatternParser("{" + name + "}");
         final String parameterName = name;

         @Override
         public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
         {
            QueryStringBuilder queryString = QueryStringBuilder.createFromEncoded(event.getAddress().getQuery())
                     .decode();

            for (String name : queryString.getParameterNames()) {
               String[] parameterValues = queryString.getParameterValues(name);
               if (parameterName.equals(name))
               {
                  if (parameterValues == null || (parameterValues.length == 0))
                  {
                     return pattern.parse("").matches();
                  }
                  else
                  {
                     for (String value : parameterValues) {

                        ParameterizedPatternResult parseResult = pattern.parse(value);
                        if (parseResult.matches())
                        {
                           return parseResult.submit(event, context);
                        }
                     }
                  }
               }
            }

            return false;
         }

         @Override
         public String toString()
         {
            return "Query.parameterExists(\"" + name + "\")";
         }

         @Override
         public Set<String> getRequiredParameterNames()
         {
            return pattern.getRequiredParameterNames();
         }

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

   /**
    * Create a {@link Condition} asserting the existence of specific parameter values within the current
    * {@link Address#getQuery()}.
    *
    * @param pattern {@link ParameterizedPattern} matching query parameter values of the current
    *           {@link Address#getQuery()}.
    *
    * @see ConfigurationRuleParameterBuilder#where(String)
    */
   public static Query valueExists(final String valuePattern)
   {
      Assert.notNull(valuePattern, "Parameter value pattern must not be null.");

      return new Query() {
         final ParameterizedPatternParser pattern = new RegexParameterizedPatternParser(valuePattern);

         @Override
         public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
         {
            QueryStringBuilder queryString = QueryStringBuilder.createFromEncoded(event.getAddress().getQuery())
                     .decode();

            for (String name : queryString.getParameterNames()) {

               for (String value : queryString.getParameterValues(name)) {
                  if (value != null && pattern.parse(value).submit(event, context))
                  {
                     return true;
                  }
               }
            }

            return false;
         }

         @Override
         public String toString()
         {
            return "Query.valueExists(\"" + valuePattern + "\")";
         }

         @Override
         public Set<String> getRequiredParameterNames()
         {
            return pattern.getRequiredParameterNames();
         }

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

}


/*
 * 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.rewrite.el.spi.BeanNameResolver;

/**
 *

 * Class implementing lazy resolving of bean names.
 *

 *

 * This class is typically created once and is than supplied to {@link LazyExpression} instances in the
 * {@link LazyExpression#LazyExpression(LazyBeanNameFinder, Class, String)} constructor.
 *

 *
 * @author Christian Kaltepoth
 */
public class LazyBeanNameFinder
{

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

   /**
    * List of all resolvers. Initialized once on object creation
    */
   private final List<BeanNameResolver> resolvers = new ArrayList<BeanNameResolver>();

   /**
    * Creates a new {@link LazyBeanNameFinder}. The constructor will find all implementations of
    * {@link ELBeanNameResolver} by using the {@link ServiceLoader} mechanism.
    *
    * @param servletContext The servlet context
    */
   public LazyBeanNameFinder(ServletContext servletContext)
   {

      // we use the context classloader
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

      // fallback, if no context classloader exists
      if (classLoader == null)
      {
         classLoader = this.getClass().getClassLoader();
      }

      // find resolvers via ServiceLoader
      @SuppressWarnings("unchecked")
      Iterator<BeanNameResolver> beanNameFinderIterator = ServiceLoader.load(BeanNameResolver.class).iterator();

      // call init() method on all resolvers
      while (beanNameFinderIterator.hasNext())
      {

         // log resolver name
         BeanNameResolver resolver = beanNameFinderIterator.next();
         if (log.isTraceEnabled())
         {
            log.trace("Initializing BeanNameResolver: " + resolver.getClass().getName());
         }

         try
         {
            resolvers.add(resolver);
         }
         catch (ClassFormatError e)
         {
            /*
             * Seems to happen for CDI classes when using GWT
             * In this case just ignore the resolver
             * See: http://code.google.com/p/prettyfaces/issues/detail?id=101
             */
            log.warn("Failed to initialize " + resolver.getClass().getSimpleName() + ": " + e.getMessage());
         }
      }
   }

   /**
    * Find the bean name of the supplied class. This method will try to resolve the bean name by calling all registered
    * implementations of {@link ELBeanNameResolver}. This method will either return the resolved name or throw an
    * {@link IllegalStateException}, if no resolver knows the name of the bean.
    *
    * @param clazz The class of the bean
    * @return The resolved bean name
    * @throws IllegalStateException If the name of the bean cannot be resolved
    */
   public String findBeanName(Class<?> clazz) throws IllegalStateException
   {

      // process all resolvers
      for (BeanNameResolver resolver : resolvers)
      {

         // try to resolve bean name with current resolver
         String name = resolver.getBeanName(clazz);

         // return the bean name, if the resolver was successful
         if (name != null)
         {
            return name;
         }

      }

      // No resolver knows the name of the bean
      throw new IllegalStateException("Cannot find name of bean '" + clazz.getName()
               + "'! You should place a @URLBeanName annotation on this class to let PrettyFaces know its name.");

   }

}


/*
 * 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.io.File;
import java.io.FileFilter;
import java.util.Set;

import org.ocpsoft.common.util.Assert;
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.ParameterizedPatternBuilder;
import org.ocpsoft.rewrite.param.ParameterizedPatternParser;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;

/**
 * A {@link Condition} responsible for determining existence of {@link File} paths on the host file-system.
 *
 * @author Lincoln Baxter, III
 */
public class Filesystem implements Condition, Parameterized
{
   private final ParameterizedPatternParser resource;
   private final FileFilter filter;

   private Filesystem(final File resource, FileFilter filter)
   {
      Assert.notNull(resource, "File path to inspect must not be null.");
      Assert.notNull(filter, "FileFilter must not be null.");
      this.resource = new RegexParameterizedPatternParser(getCanonicalPath(resource));
      this.filter = filter;
   }

   @Override
   public boolean evaluate(final Rewrite event, final EvaluationContext context)
   {
      if (resource != null)
      {
         ParameterizedPatternBuilder builder = resource.getBuilder();
         if (builder.isParameterComplete(event, context))
         {
            String filePath = builder.build(event, context);
            File file = new File(filePath);
            return filter.accept(file);
         }
      }
      return false;
   }

   /**
    * Create a {@link Condition} that returns true if the given {@link File} exists in the file-system of
    * the host environment, and is a normal file.
    *
    *

    * The given file path may be parameterized:
    *

    *
    *    new File("/tmp/file.txt")

    *    new File("c:\tmp\{param}.txt")

    *    ...
    *
    *

    *
    * @param location {@link ParameterizedPattern} specifying the location of the {@link File}.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static Filesystem fileExists(final File resource)
   {
      return new Filesystem(resource, new FileFilter()
      {
         @Override
         public boolean accept(File file)
         {
            return file.exists() && file.isFile();
         }

         @Override
         public String toString()
         {
            return "Filesystem.fileExists(" + resource.getAbsolutePath() + ")";
         }
      });
   }

   /**
    * Create a {@link Condition} that returns true if the given {@link File} exists in the file-system of
    * the host environment, and is a directory.
    *
    *

    * The given file path may be parameterized:
    *

    *
    *    new File("/tmp/directory/")

    *    new File("c:\tmp\{param}")

    *    ...
    *
    *

    *
    * @param location {@link ParameterizedPattern} specifying the location of the directory.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static Filesystem directoryExists(final File resource)
   {
      return new Filesystem(resource, new FileFilter()
      {
         @Override
         public boolean accept(File file)
         {
            return file.exists() && file.isDirectory();
         }

         @Override
         public String toString()
         {
            return "Filesystem.directoryExists(" + resource.getAbsolutePath() + ")";
         }
      });
   }

   private String getCanonicalPath(final File resource)
   {
      String path = resource.getPath().replace(System.getProperty("file.separator"), "/");
      return new File(path).getAbsolutePath().replace(System.getProperty("file.separator"), "/");
   }

   /**
    * Get the {@link ParameterizedPattern} of this {@link Filesystem}.
    */
   public ParameterizedPatternParser getExpression()
   {
      return resource;
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      return resource.getRequiredParameterNames();
   }

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

}


/*
 * 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.url;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.ocpsoft.urlbuilder.util.Decoder;
import org.ocpsoft.urlbuilder.util.Encoder;

import com.ocpsoft.pretty.faces.util.StringUtils;

/**
 * Utility class providing common functionality required when interacting with and asking questions of URLs.
 *
 * @author Lincoln Baxter, III
 */
public class URL
{
   private Metadata metadata = new Metadata();
   private String originalURL = "";
   private List<String> segments;

   private final Map<String, List<String>> decodedSegments = new LinkedHashMap<String, List<String>>();

   /**
    * Create a URL object for the given url String. The input string must not yet have been decoded.
    *
    * @param url The raw, un-decoded url String
    */
   public URL(String url)
   {
      if (url != null)
      {
         url = url.trim();
         originalURL = url;
         if (StringUtils.hasLeadingSlash(url))
         {
            metadata.setLeadingSlash(true);
            url = url.substring(1);
         }
         if (StringUtils.hasTrailingSlash(url))
         {
            metadata.setTrailingSlash(true);
            url = url.substring(0, url.length() - 1);
         }

         this.segments = Arrays.asList(StringUtils.splitBySlash(url));
      }
      else
      {
         throw new IllegalArgumentException("URL cannot be null.");
      }
   }

   public static URL build(final String url)
   {
      return new URL(url);
   }

   /**
    * Create a URL object for the given url segments (separated by '/' from the original url string), using the given
    * metadata object to represent the encoding and leading/trailing slash information about this URL.
    */
   public URL(final List<String> segments, final Metadata metadata)
   {
      this.metadata = metadata;
      this.segments = segments;
      this.originalURL = metadata.buildURLFromSegments(segments);
   }

   public static URL build(final List<String> segments, final Metadata metadata)
   {
      return new URL(segments, metadata);
   }

   /**
    * Get a list of all decoded segments (separated by '/') in this URL.
    */
   public List<String> getDecodedSegments()
   {
      String encoding = metadata.getEncoding();
      if (!decodedSegments.containsKey(encoding))
      {
         List<String> result = new ArrayList<String>();
         for (String segment : segments)
         {
            result.add(Decoder.path(segment));
         }
         decodedSegments.put(encoding, Collections.unmodifiableList(result));
      }
      return decodedSegments.get(encoding);
   }

   /**
    * Get a list of all encoded segments (separated by '/') in this URL.
    */
   public List<String> getEncodedSegments()
   {
      List<String> resultSegments = new ArrayList<String>();
      for (String segment : segments)
      {
         resultSegments.add(Encoder.path(segment));
      }
      return resultSegments;
   }

   /**
    * Return a decoded form of this URL.
    */
   public URL decode()
   {
      return new URL(getDecodedSegments(), metadata);
   }

   /**
    * Return an encoded form of this URL.
    */
   public URL encode()
   {
      return new URL(getEncodedSegments(), metadata);
   }

   /**
    * Get the number of segments (separated by '/') in this URL
    */
   public int numSegments()
   {
      return segments.size();
   }

   /**
    * Return a String representation of this URL
    */
   @Override
   public String toString()
   {
      return toURL();
   }

   /*
    * Getters & Setters
    */

   /**
    * Return a String representation of this URL
    */
   public String toURL()
   {
      return originalURL;
   }

   /**
    * Return all segments (separated by '/') in this URL
    *
    * @return
    */
   public List<String> getSegments()
   {
      return Collections.unmodifiableList(segments);
   }

   /**
    * Return true if this URL begins with '/'
    */
   public boolean hasLeadingSlash()
   {
      return metadata.hasLeadingSlash();
   }

   /**
    * Return true if this URL ends with '/'
    */
   public boolean hasTrailingSlash()
   {
      return metadata.hasTrailingSlash();
   }

   /**
    * Get the character encoding of this URL (default UTF-8)
    */
   public String getEncoding()
   {
      return metadata.getEncoding();
   }

   /**
    * Set the character encoding of this URL (default UTF-8)
    */
   public void setEncoding(final String encoding)
   {
      metadata.setEncoding(encoding);
   }

   /**
    * Get the {@link Metadata} object for this URL
    */
   public Metadata getMetadata()
   {
      return metadata;
   }

   /**
    * Set the {@link Metadata} object for this URL
    */
   public void setMetadata(final Metadata metadata)
   {
      this.metadata = metadata;
   }
}


/*
 * 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.faces;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.context.FacesContext;

import org.ocpsoft.rewrite.el.spi.ExpressionLanguageProvider;

/**
 * Implementation of {@link ExpressionLanguageProvider} that uses the {@link FacesContext} to obtain the
 * {@link ELContext} and the {@link ExpressionFactory}.
 *
 * @author Christian Kaltepoth
 */
public class FacesExpressionLanguageProvider implements ExpressionLanguageProvider
{

   @Override
   public int priority()
   {
      return 30;
   }

   @Override
   public Object retrieveValue(String expression) throws UnsupportedOperationException
   {
      FacesContext facesContext = getFacesContext();
      ELContext elContext = facesContext.getELContext();
      return getValueExpression(facesContext, expression).getValue(elContext);
   }

   @Override
   public void submitValue(String expression, Object value) throws UnsupportedOperationException
   {
      FacesContext facesContext = getFacesContext();
      ELContext elContext = facesContext.getELContext();

      ValueExpression valueExpression = getValueExpression(facesContext, expression);
      Class<?> referencedType = valueExpression.getType(elContext);

      // the value that will be injected
      Object toInject = null;

      // the expression is referencing an array
      if (referencedType.isArray()) {

         // ensure the value that will be injected is an array
         if (value != null && !value.getClass().isArray()) {
            toInject = new Object[] { value };
         }
         else {
            toInject = value;
         }

      }

      // expression is not referencing an array
      else {

         // if value to inject is an array, just use the first element
         if (value != null && value.getClass().isArray()) {
            Object[] valueAsArray = (Object[]) value;
            if (valueAsArray.length > 0) {
               toInject = valueAsArray[0];
            }
            else {
               toInject = null;
            }
         }

         // simple case: neither the expression is referencing an array nor the value is one
         else {
            toInject = value;
         }

      }

      // set the value
      Object coercedValue = facesContext.getApplication().getExpressionFactory().coerceToType(toInject, referencedType);
      valueExpression.setValue(elContext, coercedValue);

   }

   @Override
   public Object evaluateMethodExpression(String expression) throws UnsupportedOperationException
   {
      return evaluateMethodExpression(expression, new Object[0]);
   }

   @Override
   public Object evaluateMethodExpression(String expression, Object... values) throws UnsupportedOperationException
   {
      String el = toELExpression(expression);
      FacesContext facesContext = getFacesContext();
      ELContext elContext = facesContext.getELContext();
      ExpressionFactory expressionFactory = facesContext.getApplication().getExpressionFactory();
      MethodExpression methodExpression = expressionFactory.createMethodExpression(elContext, el,
               Object.class, new Class[values.length]);
      return methodExpression.invoke(elContext, values);
   }

   /**
    * Creates an {@link ValueExpression} for the supplied EL expression
    */
   private ValueExpression getValueExpression(FacesContext facesContext, String expression)
   {
      String el = toELExpression(expression);
      ExpressionFactory expressionFactory = facesContext.getApplication().getExpressionFactory();
      return expressionFactory.createValueExpression(facesContext.getELContext(), el, Object.class);
   }

   /**
    * Obtains the {@link FacesContext} using {@link FacesContext#getCurrentInstance()}. This method will throw an
    * {@link IllegalArgumentException} if the {@link FacesContext} is not available.
    */
   private FacesContext getFacesContext()
   {
      FacesContext facesContext = FacesContext.getCurrentInstance();
      if (facesContext == null) {
         throw new IllegalArgumentException(
                  "FacesContext.getCurrentInstance() returned null. EL expressions can only be evaluated in the JSF lifecycle. "
                           + "You should use PhaseAction and PhaseBinding to perform a deferred operation instead.");
      }
      return facesContext;
   }

   /**
    * Adds #{..} to the expression if required
    */
   private String toELExpression(String s)
   {
      if (s != null && !s.startsWith("#{")) {
         return "#{" + s + "}";
      }
      return s;
   }
}


/*
 * 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.rewrite;

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

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

import com.ocpsoft.pretty.faces.config.rewrite.RewriteRule;
import com.ocpsoft.pretty.faces.rewrite.processor.CaseProcessor;
import com.ocpsoft.pretty.faces.rewrite.processor.CustomClassProcessor;
import com.ocpsoft.pretty.faces.rewrite.processor.RegexProcessor;
import com.ocpsoft.pretty.faces.rewrite.processor.TrailingSlashProcessor;
import com.ocpsoft.pretty.faces.rewrite.processor.UrlProcessor;

/**
 * Process URL rewrites based on configuration
 *
 * @author Lincoln Baxter, III <lincoln@ocpsoft.com>
 */
public class RewriteEngine
{
   private static List<Processor> processors;

   static
   {
      List<Processor> list = new ArrayList<Processor>();
      list.add(new RegexProcessor());
      list.add(new CaseProcessor());
      list.add(new TrailingSlashProcessor());
      list.add(new CustomClassProcessor());
      list.add(new UrlProcessor());
      processors = Collections.unmodifiableList(list);
   }

   /**
    * Rewrite the given URL using the provided {@link RewriteRule} object as a set of rules.
    *
    * @return The rewritten URL, or the unchanged URL if no action was taken.
    */
   public String processInbound(final HttpServletRequest request, final HttpServletResponse response,
            final RewriteRule rule, final String url)
   {
      String result = url;
      if ((rule != null) && rule.isInbound() && rule.matches(url))
      {
         for (Processor p : processors)
         {
            result = p.processInbound(request, response, rule, result);
         }
      }
      return result;
   }

   /**
    * Rewrite the given URL using the provided {@link RewriteRule} object. Process the URL only if the rule is set to
    * outbound="true"
    *
    * @return The rewritten URL, or the unchanged URL if no action was taken.
    */
   public String processOutbound(final HttpServletRequest request, final HttpServletResponse response,
            final RewriteRule rule, final String url)
   {
      String result = url;
      if ((rule != null) && rule.isOutbound() && rule.matches(url))
      {
         for (Processor p : processors)
         {
            result = p.processOutbound(request, response, rule, result);
         }
      }
      return result;
   }

}


/*
 * 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 org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Flow;
import org.ocpsoft.rewrite.servlet.event.BaseRewrite.ServletRewriteFlow;

/**
 * Defines a task to be performed within a sub-{@link ServletRewriteFlow} of a given {@link ServletRewrite}
 *
 * @author Lincoln Baxter, III
 *
 */
public abstract class SubflowTask
{

   /**
    * Perform the given {@link SubflowTask} using the given {@link ServletRewrite} event. If modified, the original
    * {@link ServletRewriteFlow} will be restored after task completion.
    *
    * @param event the {@link ServletRewrite} event used in the given {@link SubflowTask}
    *
    * @return the resultant {@link ServletRewriteFlow} from the {@link SubflowTask} execution.
    */
   public static Flow perform(ServletRewrite<?, ?> rewrite, EvaluationContext context,
            SubflowTask subflowTask)
   {
      return perform(rewrite, context, rewrite.getFlow(), subflowTask);
   }

   /**
    * Perform the given {@link SubflowTask} using the given {@link ServletRewrite} event initialized with a new
    * {@link ServletRewriteFlow}. The original {@link ServletRewriteFlow} will be restored after task completion.
    *
    * @param event the {@link ServletRewrite} event used in the given {@link SubflowTask}
    * @param subflow the {@link ServletRewriteFlow} to be us as the initial state for the given {@link SubflowTask}
    *
    * @return the resultant {@link ServletRewriteFlow} from the {@link SubflowTask} execution.
    */
   public static Flow perform(ServletRewrite<?, ?> event, EvaluationContext context, Flow subflow,
            SubflowTask subflowTask)
   {
      Flow flow = event.getFlow();
      try
      {
         event.setFlow(subflow);
         subflowTask.performInSubflow(event, context);
         return event.getFlow();
      }
      finally
      {
         event.setFlow(flow);
      }
   }

   /**
    * The task to be performed in a sub-{@link ServletRewriteFlow}.
    */
   public abstract void performInSubflow(ServletRewrite<?, ?> event, EvaluationContext 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.spring;

import org.ocpsoft.rewrite.el.spi.ExpressionLanguageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.expression.BeanExpressionContextAccessor;
import org.springframework.context.expression.BeanFactoryAccessor;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.expression.spel.support.StandardTypeLocator;
import org.springframework.web.context.WebApplicationContext;

/**
 * Implementation of {@link org.ocpsoft.rewrite.el.spi.ExpressionLanguageProvider} for the Spring Expression Language
 * (SpEL)
 *
 * @author Christian Kaltepoth
 */
public class SpringExpressionLanguageProvider implements ExpressionLanguageProvider
{

   /**
    * Used to parse the SpEL expressions
    */
   private final ExpressionParser parser = new SpelExpressionParser();

   @Autowired
   private WebApplicationContext applicationContext;

   @Override
   public int priority()
   {
      return 20;
   }

   @Override
   public Object retrieveValue(String expression) throws UnsupportedOperationException
   {

      try {

         Expression exp = parser.parseExpression(expression);
         return exp.getValue(getEvaluationContext());

      }
      catch (SpelEvaluationException e) {
         throw new IllegalArgumentException(e);
      }

   }

   @Override
   public void submitValue(String expression, Object value) throws UnsupportedOperationException
   {

      try {

         Expression exp = parser.parseExpression(expression);
         exp.setValue(getEvaluationContext(), value);

      }
      catch (SpelEvaluationException e) {
         throw new IllegalArgumentException(e);
      }

   }

   @Override
   public Object evaluateMethodExpression(String expression) throws UnsupportedOperationException
   {

      try {

         // the method expression MUST end with ()
         String el = expression;
         if (!el.endsWith("()")) {
            el = el + "()";
         }

         // evaluate the expression
         Expression exp = parser.parseExpression(el);
         return exp.getValue(getEvaluationContext());

      }
      catch (SpelEvaluationException e) {
         throw new UnsupportedOperationException(e);
      }

   }

   @Override
   public Object evaluateMethodExpression(String expression, Object... values) throws UnsupportedOperationException
   {
      throw new UnsupportedOperationException();
   }

   /**
    * Lazily initialized by {@link #getEvaluationContext()}
    */
   private EvaluationContext _evaluationContext = null;

   /**
    * Lazily creates a StandardEvaluationContext. The code has been inspired by
    * {@link StandardBeanExpressionResolver#evaluate(String, BeanExpressionContext)}
    */
   public EvaluationContext getEvaluationContext()
   {

      if (_evaluationContext == null) {

         // we need a ConfigurableBeanFactory to build the BeanExpressionContext
         ConfigurableBeanFactory beanFactory = null;

         // the WebApplicationContext MAY implement ConfigurableBeanFactory
         if (applicationContext instanceof ConfigurableBeanFactory) {
            beanFactory = (ConfigurableBeanFactory) applicationContext;
         }

         // the AutowireCapableBeanFactory usually implements ConfigurableListableBeanFactory
         if (beanFactory == null && applicationContext != null
                  && applicationContext.getAutowireCapableBeanFactory() instanceof ConfigurableBeanFactory) {
            beanFactory = (ConfigurableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
         }

         // we cannot continue without a ConfigurableBeanFactory
         if (beanFactory == null) {
            throw new IllegalStateException("Unable to find a ConfigurableBeanFactory");
         }

         BeanExpressionContext beanEvaluationContext = new BeanExpressionContext(beanFactory, null);

         StandardEvaluationContext sec = new StandardEvaluationContext();
         sec.setRootObject(beanEvaluationContext);
         sec.addPropertyAccessor(new BeanExpressionContextAccessor());
         sec.addPropertyAccessor(new BeanFactoryAccessor());
         sec.addPropertyAccessor(new MapAccessor());
         sec.setBeanResolver(new BeanFactoryResolver(beanEvaluationContext.getBeanFactory()));
         sec.setTypeLocator(new StandardTypeLocator(beanEvaluationContext.getBeanFactory().getBeanClassLoader()));
         ConversionService conversionService = beanEvaluationContext.getBeanFactory().getConversionService();
         if (conversionService != null) {
            sec.setTypeConverter(new StandardTypeConverter(conversionService));
         }

         _evaluationContext = sec;
      }

      return _evaluationContext;

   }

}


/*
 * 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 org.ocpsoft.rewrite.util.Visitor;

/**
 * This class allows {@link Visitor}s to walk through an {@link Operation} tree which may contain
 * {@link CompositeOperation} elements.
 *
 * @author Lincoln Baxter, III
 */
public class OperationVisit
{
   private final Operation root;

   /**
    * Initialize class with the supplied root {@link Operation}
    */
   public OperationVisit(Operation root)
   {
      this.root = root;
   }

   /**
    * Submit the supplied visitor and apply it to all {@link Operation} instances in the tree
    */
   public void accept(Visitor<Operation> visitor)
   {
      visit(root, visitor);
   }

   /**
    * Method to call the visitor for the supplied {@link Operation} and recursively calls itself, if the
    * {@link Operation} is a {@link CompositeOperation}.
    */
   private void visit(Operation operation, Visitor<Operation> visitor)
   {
      visitor.visit(operation);

      if (operation instanceof CompositeOperation) {
         if (((CompositeOperation) operation).getOperations() != null) {
            for (Operation child : ((CompositeOperation) operation).getOperations()) {
               visit(child, visitor);
            }
         }
      }
   }

}


/*
 * 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.scan;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;

import org.ocpsoft.logging.Logger;

/**
 *

 * This class reads Java class files and checks whether they contain references to specific annotations. This allows to
 * check classes for the presence of annotations before instantiating them.
 *

 *
 *

 * The filter is inspired by the ClassByteCodeAnnotationFilter of Apache MyFaces 2.0. Many thanks go out to
 * Leonardo Uribe for his great work on this class.
 *

 *
 * @see http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html
 * @see http://en.wikipedia.org/wiki/Class_%28file_format%29
 *
 * @author Christian Kaltepoth
 */
public class ByteCodeFilter
{

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

   /**
    * The magic to identify class files.
    */
   private final static int CLASS_FILE_MAGIC = 0xCAFEBABE;

   /*
    * Tag values for the Constant Pool:
    * http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#20080
    */
   private final static int CONSTANT_Class = 7;
   private final static int CONSTANT_Fieldref = 9;
   private final static int CONSTANT_Methodref = 10;
   private final static int CONSTANT_InterfaceMethodref = 11;
   private final static int CONSTANT_String = 8;
   private final static int CONSTANT_Integer = 3;
   private final static int CONSTANT_Float = 4;
   private final static int CONSTANT_Long = 5;
   private final static int CONSTANT_Double = 6;
   private final static int CONSTANT_NameAndType = 12;
   private final static int CONSTANT_Utf8 = 1;
   private static final int CONSTANT_MethodHandle = 15;
   private static final int CONSTANT_MethodType = 16;
   private static final int CONSTANT_InvokeDynamic = 18;

   /**
    * The strings to look for in the constants table
    */
   private final Set<String> fieldDescriptors = new LinkedHashSet<String>();

   /**
    * The filter must be initialized with a list of types to look for when scanning the class files. If a class files
    * contains a reference to one of these types, the filter will accept the class.
    *
    * @param types A Set of types to look for
    */
   public ByteCodeFilter(Set<Class<? extends Annotation>> types)
   {
      for (Class<? extends Annotation> type : types) {
         fieldDescriptors.add("L" + type.getName().replace('.', '/'));
      }
   }

   /**
    *

    * Checks whether that supplied {@link InputStream} contains a Java class file that might contain references to
    * specific annotation types.
    *

    *

    * The caller of this method is responsible to close the supplied {@link InputStream}. This method won't do it!
    *

    *
    * @param classFileStream The stream to read the class file from.
    * @return true for files that contain at least one reference to one of the "interesting" annotations
    * @throws IOException for any kind of IO problem
    */
   @SuppressWarnings("unused")
   public boolean accept(InputStream classFileStream) throws IOException
   {

      // open a DataInputStream
      DataInputStream in = new DataInputStream(classFileStream);

      // read magic and abort if it doesn't match
      int magic = in.readInt();
      if (magic != CLASS_FILE_MAGIC)
      {
         if (log.isDebugEnabled())
         {
            log.debug("Magic not found! Not a valid class file!");
         }
         return false;
      }

      // check for at least JDK 1.5
      int minor = in.readUnsignedShort();
      int major = in.readUnsignedShort();
      if (major < 49)
      {
         // JDK 1.4 or less
         if (log.isTraceEnabled())
         {
            log.trace("Not a JDK5 class! It cannot contain annotations!");
         }
         return false;
      }

      // this values is equal to the number entries in the constants pool + 1
      int constantPoolEntries = in.readUnsignedShort() - 1;

      // loop over all entries in the constants pool
      for (int i = 0; i < constantPoolEntries; i++)
      {

         // the tag to identify the record type
         int tag = in.readUnsignedByte();

         // process record according to its type
         switch (tag)
         {

         case CONSTANT_Class:
            /*
             * CONSTANT_Class_info {
             *   u1 tag;
             *   u2 name_index;
             * }
             */
            in.readUnsignedShort();
            break;

         case CONSTANT_Fieldref:
         case CONSTANT_Methodref:
         case CONSTANT_InterfaceMethodref:
            /*
             * CONSTANT_[Fieldref|Methodref|InterfaceMethodref]_info {
             *   u1 tag;
             *   u2 class_index;
             *   u2 name_and_type_index;
             * }
             */
            in.readUnsignedShort();
            in.readUnsignedShort();
            break;

         case CONSTANT_String:
            /*
             * CONSTANT_String_info {
             *   u1 tag;
             *   u2 string_index;
             * }
             */
            in.readUnsignedShort();
            break;

         case CONSTANT_Integer:
         case CONSTANT_Float:
            /*
             * CONSTANT_[Integer|Float]_info {
             *   u1 tag;
             *   u4 bytes;
             * }
             */
            in.readInt();
            break;

         case CONSTANT_Long:
         case CONSTANT_Double:

            /*
             * CONSTANT_Long_info {
             *   u1 tag;
             *   u4 high_bytes;
             *   u4 low_bytes;
             * }
             */
            in.readLong();

            /*
             * We must increase the constant pool index because this tag
             * type takes two entries
             */
            i++;

            break;

         case CONSTANT_NameAndType:
            /*
             * CONSTANT_NameAndType_info {
             *   u1 tag;
             *   u2 name_index;
             *   u2 descriptor_index;
             * }
             */
            in.readUnsignedShort();
            in.readUnsignedShort();
            break;

         case CONSTANT_Utf8:
            /*
             * CONSTANT_Utf8_info {
             *   u1 tag;
             *   u2 length;
             *   u1 bytes[length];
             * }
             */
            String str = in.readUTF();

            // check if this string sounds interesting
            if (containsFieldDescriptor(str))
            {
               if (log.isTraceEnabled())
               {
                  log.trace("Found interesting annotation reference in constant pool: " + str);
               }
               return true;
            }
            break;

         case CONSTANT_MethodHandle:
           /*
            * CONSTANT_MethodHandle_info {
            *     u1 tag;
            *     u1 reference_kind;
            *     u2 reference_index;
            * }
            */
           in.readByte();
           in.readShort();
           break;

         case CONSTANT_MethodType:
           /*
            * CONSTANT_MethodType_info {
            *     u1 tag;
            *     u2 descriptor_index;
            * }
            */
           in.readShort();
           break;

         case CONSTANT_InvokeDynamic:
           /*
            * CONSTANT_InvokeDynamic_info {
            *     u1 tag;
            *     u2 bootstrap_method_attr_index;
            *     u2 name_and_type_index;
            * }
            */
           in.readShort();
           in.readShort();
           break;

         default:
            /*
             * Unknown tag! Should not happen! We will scan the class in this case.
             */
            if (log.isDebugEnabled())
            {
               log.debug("Unknown constant pool tag found: " + tag);
            }
            return true;
         }
      }

      return false;

   }

   /**
    * Returns true if the given string contains a field descriptor of one of the annotations we are looking for.
    */
   private boolean containsFieldDescriptor(String str)
   {
      for (String descriptor : fieldDescriptors) {
         if (str.contains(descriptor)) {
            return true;
         }
      }
      return false;
   }

   @Override
   public String toString()
   {
      return "ByteCodeFilter [fieldDescriptors=" + fieldDescriptors + "]";
   }

}


/*
 * 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.Set;

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.InboundRewrite;
import org.ocpsoft.rewrite.param.ConfigurableParameter;
import org.ocpsoft.rewrite.param.Parameter;
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.RegexParameterizedPatternBuilder;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.config.bind.RequestBinding;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.urlbuilder.Address;

/**
 * A {@link Condition} that inspects the entire value of the current {@link Address}.
 *
 * @author Lincoln Baxter, III
 */
public abstract class URL extends HttpCondition implements Parameterized
{
   private final ParameterizedPatternParser expression;
   private boolean requestBinding;

   private URL(final String pattern)
   {
      Assert.notNull(pattern, "URL must not be null.");
      this.expression = new RegexParameterizedPatternParser(".*", pattern);
   }

   /**
    * Create a {@link Condition} that inspects the entire value of the current {@link Address}, comparing against the
    * given pattern.
    *

    * The given pattern may be parameterized:
    *

    * INBOUND:

    *
    *    /path/store?item=1436
    *    /path/store?item={itemId}

    *    /path/{store}?item={itemId}&category={catId}

    *    ...
    *
    *

    * OUTBOUND:

    *
    * http://domain.com/path/store.html?item=1436
    * http://domain.com/path/store?item={itemId}

    * /path/{store}?item={itemId}&category={catId}

    * ...
    *
    *

    *
    * @param url {@link ParameterizedPattern} to which the current {@link Address} must match.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static URL matches(final String pattern)
   {
      return new URL(pattern) {
         @Override
         public String toString()
         {
            return "URL.matches(\"" + pattern + "\")";
         }
      };
   }

   /**
    * Create a {@link Condition} to capture the entire value of the current {@link Address} into the given
    * {@link Parameter} name. This {@link Parameter} may be referenced in other {@link Condition} and {@link Operation}
    * instances within the current {@link Rule}.
    *
    * @see ConfigurationRuleParameterBuilder#where(String)
    */
   public static URL captureIn(final String param)
   {
      URL path = new URL("{" + param + "}") {
         @Override
         public String toString()
         {
            return "URL.captureIn(\"" + param + "\")";
         }
      };
      return path;
   }

   /**
    * Bind each {@link Parameter} value of the current {@link Address} to the corresponding request parameter by name.
    * Only takes affect on {@link InboundRewrite} events.
    */
   public URL withRequestBinding()
   {
      this.requestBinding = true;
      return this;
   }

   @Override
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      String requestURL = null;

      if (event instanceof HttpOutboundServletRewrite)
      {
         requestURL = ((HttpOutboundServletRewrite) event).getOutboundAddress().toString();
         String contextPath = event.getContextPath();
         if (!contextPath.equals("/") && requestURL.startsWith(contextPath))
         {
            requestURL = requestURL.substring(event.getContextPath().length());
         }
      }
      else
      {
         requestURL = event.getAddress().toString();
      }

      return expression.parse(requestURL).submit(event, context);
   }

   /**
    * Get the underlying {@link RegexParameterizedPatternBuilder} for this {@link URL}
    *

    * See also: {@link #where(String)}
    */
   public ParameterizedPatternParser getSchemeExpression()
   {
      return expression;
   }

   public ParameterizedPatternParser getPathExpression()
   {
      return expression;
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      return expression.getRequiredParameterNames();
   }

   @Override
   public void setParameterStore(ParameterStore store)
   {
      if (requestBinding)
      {
         for (String param : getRequiredParameterNames()) {
            Parameter<?> parameter = store.get(param);
            if (parameter instanceof ConfigurableParameter<?>)
               ((ConfigurableParameter<?>) parameter).bindsTo(RequestBinding.parameter(param));
         }
      }

      expression.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.servlet.config;

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.param.ConfigurableParameter;
import org.ocpsoft.rewrite.param.Parameter;
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.RegexConstraint;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.config.bind.RequestBinding;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.spi.RequestParameterProvider;
import org.ocpsoft.urlbuilder.Address;
import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * A {@link Condition} that inspects the value of {@link HttpServletRewrite#getRequestPath()}
 *
 * @author Lincoln Baxter, III
 */
public abstract class Path extends HttpCondition implements Parameterized
{
   private final ParameterizedPatternParser expression;
   private boolean withRequestBinding = false;
   private String captureIn;

   private Path(final String pattern)
   {
      Assert.notNull(pattern, "Path must not be null.");
      this.expression = new RegexParameterizedPatternParser("[^/]+", pattern);
   }

   /**
    * Create a {@link Condition} that compares the current {@link Address#getPath()} to the given pattern.
    *

    * The given pattern may be parameterized:
    *

    *
    *    /example/{param}

    *    /example/{param1}/sub/{param2}

    *    ...
    *
    *

    *
    * @param pattern {@link ParameterizedPattern} matching the path.
    *
    * @see ConfigurationRuleParameterBuilder#where(String)
    */
   public static Path matches(final String pattern)
   {
      return new Path(pattern) {
         @Override
         public String toString()
         {
            return "Path.matches(\"" + pattern + "\")";
         }
      };
   }

   /**
    * Capture the entire path portion of the {@link Address} into the given {@link Parameter}.
    *
    * @param param the name of the {@link Parameter} to which the entire path portion of the {@link Address} will be
    *           bound.
    */
   public static Path captureIn(final String param)
   {
      Path path = new Path("{" + param + "}") {
         @Override
         public String toString()
         {
            return "Path.captureIn(\"" + param + "\")";
         }
      };
      path.captureIn = param;
      return path;
   }

   /**
    * Bind each path {@link Parameter} to the corresponding request parameter by name.
    *

    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)} {@link HttpServletRequest#getParameterMap()}
    *      {@link RequestParameterProvider}
    */
   public Path withRequestBinding()
   {
      withRequestBinding = true;
      return this;
   }

   @Override
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      String url = null;

      if (event instanceof HttpOutboundServletRewrite)
      {
         url = ((HttpOutboundServletRewrite) event).getOutboundAddress().getPath();
         if (url == null) // e.g an external url like http://ocpsoft.org (without trailing slash) or an anchor link have
                          // a null path
            return false;
      }
      else
         url = AddressBuilder.begin().pathDecoded(event.getInboundAddress().getPath()).buildLiteral().toString();

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

      return expression.parse(url).submit(event, context);
   }

   /**
    * Get the underlying {@link ParameterizedPattern} for this {@link Path}
    */
   public ParameterizedPatternParser getExpression()
   {
      return expression;
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      Set<String> result = new LinkedHashSet<String>();
      if (captureIn != null)
         result.add(captureIn);
      else
         result.addAll(expression.getRequiredParameterNames());
      return result;
   }

   @Override
   public void setParameterStore(ParameterStore store)
   {
      if (captureIn != null)
      {
         Parameter<?> parameter = store.get(captureIn);
         if (parameter instanceof ConfigurableParameter<?>)
            ((ConfigurableParameter<?>) parameter).constrainedBy(new RegexConstraint(".*"));
      }
      if (withRequestBinding)
      {
         for (String param : getRequiredParameterNames()) {
            Parameter<?> parameter = store.get(param);
            if (parameter instanceof ConfigurableParameter<?>)
               ((ConfigurableParameter<?>) parameter).bindsTo(RequestBinding.parameter(param));
         }
         withRequestBinding = true;
      }

      expression.setParameterStore(store);
   }
}


/*
 * 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.config.reload;

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

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ocpsoft.common.services.ServiceLoader;

import com.ocpsoft.pretty.faces.config.PrettyConfigurator;
import com.ocpsoft.pretty.faces.spi.DevelopmentModeDetector;

/**
 * This class reloads the PrettyFaces configuration if PrettyFaces runs in development mode.
 *
 * @author Christian Kaltepoth
 */
public class PrettyConfigReloader
{

   private final Log log = LogFactory.getLog(this.getClass());

   /**
    * The configuration is reloaded after this amount of time
    */
   private final static long CONFIG_RELOAD_DELAY = 2000l;

   /**
    * Keeps track of the last time the configuration was updated
    */
   private long lastUpdate = 0;

   /**
    * The development mode will be lazily detected
    */
   private Boolean developmentMode = null;

   public void reloadIfNecessary(ServletContext servletContext)
   {
      // the development mode detection is started when the first request is received
      if (developmentMode == null) {

         developmentMode = isDevelopmentModeActive(servletContext);

         if (log.isDebugEnabled()) {
            if (developmentMode) {
               log.debug("PrettyFaces development mode detected! Configuration reloading will be enabled.");
            }
            else {
               log.debug("No development mode detected. Configuration reloading gets disabled.");
            }
         }

      }

      // reloading is only done if in development mode
      if (developmentMode != null && developmentMode.booleanValue()) {

         // the point in time the configuration will be reloaded
         long nextUpdate = lastUpdate + CONFIG_RELOAD_DELAY;

         if (System.currentTimeMillis() > nextUpdate)
         {

            if (log.isDebugEnabled()) {
               log.debug("Reloading PrettyFaces configuration...");
            }

            /*
             * first update the 'lastUpdate' so that concurrent requests won't
             * also do an update of the configuration.
             */
            lastUpdate = System.currentTimeMillis();

            // run the configuration procedure again
            PrettyConfigurator configurator = new PrettyConfigurator(servletContext);
            configurator.configure();

         }
      }

   }

   /**
    * Detects the PrettyFaces development mode using the {@link DevelopmentModeDetector} SPI.
    */
   private boolean isDevelopmentModeActive(ServletContext servletContext)
   {

      // create the ServiceLoader for the SPI
      ServiceLoader<DevelopmentModeDetector> serviceLoader = ServiceLoader.loadTypesafe(DevelopmentModeDetector.class);

      // we need a list to be able to sort it
      List<DevelopmentModeDetector> detectors = new ArrayList<DevelopmentModeDetector>();
      for (DevelopmentModeDetector detector : serviceLoader) {
         detectors.add(detector);
      }

      // sort them by priority
      Collections.sort(detectors, new Comparator<DevelopmentModeDetector>() {
         public int compare(DevelopmentModeDetector left, DevelopmentModeDetector right)
         {
            return left.getPriority() - right.getPriority();
         }
      });

      // process the detectors until one returns a result != null
      for (DevelopmentModeDetector detector : detectors) {

         Boolean result = detector.isDevelopmentMode(servletContext);

         if (log.isDebugEnabled()) {
            log.debug("Detector " + detector.getClass().getSimpleName() + " returned: " + result);
         }

         if (result != null) {
            return result.booleanValue();
         }

      }

      // default value
      return false;

   }

}


/*
 * 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.urlbuilder;

/**
 * An {@link Address} with a domain section.
 *
 * @author Lincoln Baxter, III
 */
public class AddressBuilderDomain implements BuildableAddress
{
   private AddressBuilder parent;

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

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

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

   /**
    * Set a parameter name and value or values. The supplied values will be stored without additional encoding.
    */
   public AddressBuilderDomain 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 AddressBuilderDomain 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 AddressBuilderDomain setEncoded(CharSequence name, Object... values)
   {
      parent.setEncoded(name, values);
      return this;
   }

   /**
    * Set the port section of this {@link Address}.
    */
   public AddressBuilderPort port(int port)
   {
      return parent.port(port);
   }

   /**
    * Set the path section of this {@link Address}. The given value will be stored without additional encoding or
    * decoding.
    */
   public AddressBuilderPath path(CharSequence path)
   {
      return parent.path(path);
   }

   /**
    * Set the path section of this {@link Address}. The given value will be decoded before it is stored.
    */
   public AddressBuilderPath pathDecoded(CharSequence path)
   {
      return parent.pathDecoded(path);
   }

   /**
    * Set the path section of this {@link Address}. The given value will be encoded before it is stored.
    */
   public AddressBuilderPath pathEncoded(CharSequence path)
   {
      return parent.pathEncoded(path);
   }

   /**
    * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional
    * encoding or decoding.
    */
   public AddressBuilderQuery query(CharSequence name, Object... values)
   {
      return parent.query(name, values);
   }

   /**
    * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored.
    */
   public AddressBuilderQuery queryDecoded(CharSequence name, Object... values)
   {
      return parent.queryDecoded(name, values);
   }

   /**
    * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored.
    */
   public AddressBuilderQuery queryEncoded(CharSequence name, Object... values)
   {
      return parent.queryEncoded(name, values);
   }

   /**
    * Set a literal query string without additional encoding or decoding. A leading '?' character is optional; the
    * builder will add one if necessary.
    */
   public AddressBuilderQuery queryLiteral(String query)
   {
      return parent.queryLiteral(query);
   }

   /**
    * Set the anchor section of this {@link Address}.
    */
   public AddressBuilderAnchor anchor(String anchor)
   {
      return parent.anchor(anchor);
   }

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

}