/*
 * 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.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.servlet.ServletContext;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.annotation.api.ClassVisitor;
import org.ocpsoft.rewrite.annotation.spi.ClassFinder;

/**
 * Base class for implementations of the {@link ClassFinder} interface.
 *
 * @see ClassFinder
 * @author Christian Kaltepoth
 */
public abstract class AbstractClassFinder implements ClassFinder
{
   /**
    * Common logger for all implementations
    */
   protected final Logger log = Logger.getLogger(this.getClass());

   /**
    * The {@link ServletContext}
    */
   protected final ServletContext servletContext;

   /**
    * The {@link ClassLoader} to get classes from
    */
   protected final ClassLoader classLoader;

   /**
    * The filter for checking which classes to process
    */
   protected final PackageFilter packageFilter;

   /**
    * The filter to check bytecode for interesting annotations
    */
   private final ByteCodeFilter byteCodeFilter;

   /**
    * Initialization procedure
    *
    * @param servletContext The {@link ServletContext} of the web application.
    * @param classLoader The {@link ClassLoader} to use for loading classes
    * @param packageFilter The {@link PackageFilter} used to check if a package has to be scanned.
    */
   public AbstractClassFinder(ServletContext servletContext, ClassLoader classLoader, PackageFilter packageFilter,
            ByteCodeFilter byteCodeFilter)
   {
      this.servletContext = servletContext;
      this.classLoader = classLoader;
      this.packageFilter = packageFilter;
      this.byteCodeFilter = byteCodeFilter;
   }

   /**
    * Strip everything up to and including a given prefix from a string.
    *
    * @param str The string to process
    * @param prefix The prefix
    * @return the stripped string or null if the prefix has not been found
    */
   protected String stripKnownPrefix(String str, String prefix)
   {

      int startIndex = str.lastIndexOf(prefix);
      if (startIndex != -1)
      {
         return str.substring(startIndex + prefix.length());
      }

      return null;
   }

   /**
    *

    * Creates a FQCN from an {@link URL} representing a .class file.
    *

    *
    * @param url The path of the class file
    * @return the FQCN of the class
    */
   protected static String getClassName(String filename)
   {

      // end index is just before ".class"
      int endIndex = filename.length() - ".class".length();

      // extract relevant part of the path
      String relativePath = filename.substring(0, endIndex);

      // replace / by . to create FQCN
      return relativePath.replace('/', '.');
   }

   /**
    * Checks if a supplied class has to be processed by checking the package name against the {@link PackageFilter}.
    *
    * @param className FQCN of the class
    * @return true for classes to process, false for classes to ignore
    */
   protected boolean mustProcessClass(String className)
   {

      // skip versioned classes in Java 9 Multi-release JAR files
      if (className.startsWith("META-INF.versions")) {
         return false;
      }

      // the default package
      String packageName = "";

      // find last dot in class name to determine the package name
      int packageEndIndex = className.lastIndexOf(".");
      if (packageEndIndex != -1)
      {
         packageName = className.substring(0, packageEndIndex);
      }

      // check filter
      return packageFilter.isAllowedPackage(packageName);

   }

   /**
    *

    * Handle a single class to process. This method should only be called if the class name is accepted by the
    * {@link PackageFilter}.
    *

    *

    * If classFileStream is not null the method will first try to check whether the class
    * files may contain annotations by scanning it with the {@link ByteCodeFilter}. If no {@link InputStream} is
    * supplied, this check will be skipped. After that the method will create an instance of the class and then call
    * {@link ClassVisitor#visit(Class, org.ocpsoft.prettyfaces.annotation.PrettyConfig)}.
    *

    *

    * Please not the the called of this method is responsible to close the supplied {@link InputStream}!
    *

    *
    * @param className The FQCN of the class
    * @param classFileStream The Java class file of the class (may be null)
    * @param visitor the visitor
    */
   protected void processClass(String className, InputStream classFileStream, ClassVisitor visitor)
   {

      // skip Java 9 module metadata class files
      if (className.equals("module-info")) return ;

      // bytecode check is only performed if the InputStream is available
      if (classFileStream != null)
      {

         // we must take care of IOExceptions thrown by ByteCodeAnnotationFilter
         try
         {

            // call bytecode filter
            boolean shouldScanClass = byteCodeFilter.accept(classFileStream);

            // No annotations -> abort
            if (!shouldScanClass)
            {
               return;
            }

            // filter says we should scan the class
            if (log.isDebugEnabled())
            {
               log.debug("Bytecode filter recommends to scan class: " + className);
            }

         }
         catch (IOException e)
         {
            if (log.isDebugEnabled())
            {
               log.debug("Failed to parse class file: " + className, e);
            }
         }
      }

      try
      {
         // request this class from the ClassLoader
         Class<?> clazz = Class.forName(className, false, classLoader);

         // call handler
         visitor.visit(clazz);

      }
      catch (NoClassDefFoundError e)
      {
         // reference to another class unknown to the classloader
         log.debug("Could not load class '" + className + "': " + e.toString());
      }
      catch (ClassNotFoundException e)
      {
         // should no happen, because we found the class on the classpath
         throw new IllegalStateException("Unable to load class: " + className, e);
      }

   }

}


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

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.config.DefaultConditionBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;

/**
 * A {@link Condition} used to evaluate temporal statements using Joda Time
 *
 * @author Lincoln Baxter, III
 */
public abstract class JodaTime extends DefaultConditionBuilder
{
   private final TimeCondition condition;
   private DateTimeZone zone;

   /*
    * Constructors
    */
   /**
    * Create a new {@link JodaTime} with the given {@link TimeCondition}
    */
   public JodaTime(final TimeCondition condition)
   {
      this.condition = condition;
   }

   /**
    * Create a new {@link JodaTime} with the given {@link TimeCondition} and provided {@link DateTimeZone}
    */
   public JodaTime(final TimeCondition condition, final DateTimeZone zone)
   {
      this.condition = condition;
      this.zone = zone;
   }

   /*
    * Configurators
    */
   /**
    * Create a new {@link JodaTime} condition that, for each {@link org.ocpsoft.rewrite.event.Rewrite}, evaluates the
    * given {@link TimeCondition} again the system's current {@link DateTime}
    */
   public static JodaTime matches(final TimeCondition condition)
   {
      return new JodaTime(condition) {
         @Override
         public String toString()
         {
            return "JodaTime.matches(" + condition + ")";
         }
      };
   }

   /**
    * Create a new {@link JodaTime} condition that, for each {@link org.ocpsoft.rewrite.event.Rewrite}, evaluates the
    * given {@link TimeCondition} again the system's current {@link DateTime} using the provided {@link DateTimeZone}
    */
   public static JodaTime matches(final TimeCondition condition, final DateTimeZone zone)
   {
      return new JodaTime(condition, zone) {
         @Override
         public String toString()
         {
            return "JodaTime.matches(" + condition + ", " + zone + ")";
         }
      };
   }

   /*
    * Evaluator
    */
   @Override
   public boolean evaluate(final Rewrite event, final EvaluationContext context)
   {
      if (zone != null)
         return condition.matches(new DateTime(zone));
      else
         return condition.matches(new DateTime());
   }

}


/*
 * 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 query section.
 *
 * @author Lincoln Baxter, III
 */
public class AddressBuilderQuery implements BuildableAddress
{
   private AddressBuilder parent;

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

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

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

   /**
    * 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 value)
   {
      return parent.query(name, value);
   }

   /**
    * 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 value)
   {
      return parent.queryEncoded(name, value);
   }

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

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


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

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

import com.ocpsoft.pretty.PrettyContext;
import com.ocpsoft.pretty.faces.config.mapping.UrlMapping;
import com.ocpsoft.pretty.faces.config.rewrite.RewriteRule;
import com.ocpsoft.pretty.faces.url.URL;

public class PrettyConfig
{
   public static final String CONFIG_REQUEST_KEY = "pretty_CONFIG_REQUEST_KEY";
   private List<UrlMapping> mappings = new ArrayList<UrlMapping>();
   private List<RewriteRule> globalRewriteRules = new ArrayList<RewriteRule>();
   private String dynaviewId = "";
   private final Map<String, UrlMapping> cachedMappings = new LinkedHashMap<String, UrlMapping>();
   private boolean useEncodeUrlForRedirects = false;

   /**
    * Creates an empty {@link PrettyConfig} object
    */
   public PrettyConfig()
   {
      // nothing
   }

   /**
    * Set the current DynaView ID. This is used when calculating dynamic viewIds specified in pretty-config.xml (Do not
    * change unless you know what you are doing - this maps directly to your Faces Servlet mapping and is discovered
    * automatically when the rewrite system starts up.
    */
   public void setDynaviewId(final String facesDynaViewId)
   {
      this.dynaviewId = facesDynaViewId;
   }

   /**
    * Get the current DynaView ID. This is the viewId to which {@link PrettyFilter} will issue a servlet forward when
    * the developer has requested a dynamic view-id in a url-mapping.
    */
   public String getDynaviewId()
   {
      return dynaviewId;
   }

   /**
    * Return the currently configured List of {@link RewriteRule} as an unmodifiable collection.
    */
   public List<RewriteRule> getGlobalRewriteRules()
   {
      return globalRewriteRules;
   }

   /**
    * Set the current list of {@link RewriteRule} objects.
    */
   public void setGlobalRewriteRules(final List<RewriteRule> rules)
   {
      globalRewriteRules = Collections.unmodifiableList(rules);
   }

   /**
    * Get the currently configured list of {@link UrlMapping} as an unmodifiable List
    */
   public List<UrlMapping> getMappings()
   {
      return Collections.unmodifiableList(mappings);
   }

   /**
    * Set the currently configured list of {@link UrlMapping}
    */
   public void setMappings(final List<UrlMapping> mappings)
   {
      this.mappings = Collections.unmodifiableList(mappings);
   }

   /**
    * Search through all currently configured {@link UrlMapping} objects for the first one that matches the given URL.
    *
    * @return the first appropriate {@link UrlMapping} for a given URL.
    */
   public UrlMapping getMappingForUrl(final URL url)
   {
      final String mappingKey = url.toURL();
      if (cachedMappings.containsKey(mappingKey)) {
         return cachedMappings.get(mappingKey);
      }
      for (UrlMapping mapping : getMappings())
      {
         if (mapping.matches(url))
         {
            if (!mapping.getPatternParser().isElPattern()) {
               cachedMappings.put(mappingKey, mapping);
            }
            return mapping;
         }
      }
      return null;
   }

   /**
    * Discover if the given id is a {@link UrlMapping} id specified in the current configuration.
    *
    * @return True if the id is found, false if not.
    */
   public boolean isMappingId(final String id)
   {
      UrlMapping mapping = getMappingById(id);
      return mapping instanceof UrlMapping;
   }

   /**
    * Discover if the given URL is mapped by any {@link UrlMapping} specified in the current configuration.
    *
    * @return True if the URL is mapped, false if not.
    */
   public boolean isURLMapped(final URL url)
   {
      UrlMapping mapping = getMappingForUrl(url);
      return mapping != null;
   }

   /**
    * Discover if the given ViewId is mapped by any {@link UrlMapping} specified in the current configuration.
    *

    * Note:This will not match if a #{dynamicView.id} method is configured.
    *
    * @return True if the ViewId is mapped, false if not.
    */
   public boolean isViewMapped(String viewId)
   {
      if (viewId != null)
      {
         viewId = viewId.trim();
         for (UrlMapping mapping : mappings)
         {
            if (viewId.equals(mapping.getViewId())
                     || (viewId.startsWith("/") && viewId.substring(1).equals(mapping.getViewId())))
            {
               return true;
            }
         }
      }
      return false;
   }

   /**
    * Get the {@link UrlMapping} corresponding with the given mapping id.
    *
    * @param id Mapping id
    * @return PrettyUrlMapping Corresponding mapping
    */
   public UrlMapping getMappingById(String id)
   {
      if (id != null)
      {
         if (id.startsWith(PrettyContext.PRETTY_PREFIX))
         {
            id = id.substring(PrettyContext.PRETTY_PREFIX.length());
         }
         for (UrlMapping mapping : getMappings())
         {
            if (mapping.getId().equals(id))
            {
               return mapping;
            }
         }
      }
      return null;
   }

   public boolean isUseEncodeUrlForRedirects()
   {
      return useEncodeUrlForRedirects;
   }

   public void setUseEncodeUrlForRedirects(boolean useEncodeUrlForRedirects)
   {
      this.useEncodeUrlForRedirects = useEncodeUrlForRedirects;
   }

   @Override
   public String toString()
   {
      return "PrettyConfig [mappings=" + mappings + ", globalRewriteRules=" + globalRewriteRules + "]";
   }
}


/*
 * Copyright 2013 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.ocpsoft.rewrite.param;

import org.ocpsoft.rewrite.context.EvaluationContext;

/**
 * Utility for extracting values from the {@link ParameterValueStore}.
 *
 * @author Lincoln Baxter, III
 *
 */
public class Parameters
{
   /**
    * Retrieve a single parameter value from the {@link ParameterValueStore}.
    */
   public static String retrieve(EvaluationContext context, String string)
   {
      return ((ParameterValueStore) context.get(ParameterValueStore.class)).retrieve(((ParameterStore) context
               .get(ParameterStore.class)).get(string));
   }
}


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

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

/**
 *

 * This class holds different JSF utility methods.
 *

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

   /**
    * This method reads all required values from the {@link FacesContext} and
    * then delegates the call to
    * {@link #normalizeRequestURI(String, String, String)}.
    *
    * @param context
    *           The {@link FacesContext}
    * @param viewId
    *           the view id
    * @return the URI without additional FacesServlet mappings
    */
   public static String normalizeRequestURI(final FacesContext context, final String viewId)
   {
      ExternalContext externalContext = context.getExternalContext();
      return normalizeRequestURI(externalContext.getRequestServletPath(), externalContext.getRequestPathInfo(), viewId);
   }

   /**
    * Prune the url-pattern from the viewId if prefix mapped
    * FacesServlet, otherwise return the original URI.
    *
    * @param servletPath
    *           the servlet path
    * @param requestPathInfo
    *           the path info
    * @param viewId
    *           the view id
    * @return the URI without additional FacesServlet mappings
    * @see javax.servlet.http.HttpServletRequest#getServletPath()
    * @see javax.servlet.http.HttpServletRequest#getPathInfo()
    */
   public static String normalizeRequestURI(final String servletPath, final String requestPathInfo, final String viewId)
   {

      // We must not process empty viewIds
      if (viewId == null)
      {
         return null;
      }

      /*
       * code from com.sun.faces.util.Util#getMappingForRequest(String servletPath, String pathInfo)
       * and  com.sun.faces.application.view.MultiViewHandler#normalizeRequestURI(String uri, String mapping)
       */
      if (servletPath != null)
      {
         if ((requestPathInfo != null || servletPath.indexOf('.') < 0) && viewId.startsWith(servletPath + '/'))
         {
            return viewId.substring(servletPath.length());
         }
      }

      return viewId;
   }

}


/*
 * 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 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.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedPattern;
import org.ocpsoft.rewrite.param.ParameterizedPatternParser;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.urlbuilder.Address;

/**
 * A {@link Condition} that inspects the value of {@link HttpServletRequest#getScheme()}
 *
 * @author Lincoln Baxter, III
 */
public abstract class Scheme extends HttpCondition implements Parameterized
{
   private final ParameterizedPatternParser expression;

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

   /**
    * Create a {@link Condition} to inspect the current request scheme, comparing against the given pattern.
    *

    * The given pattern may be parameterized using the following format:
    *

    *
    *    https

    *    mailto

    *    {scheme}

    *    {scheme}-custom

    *    ...
    *
    *

    *
    * @param pattern {@link ParameterizedPattern} specifying the {@link Scheme} of the current {@link Address}.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static Scheme matches(final String pattern)
   {
      return new Scheme(pattern) {
         @Override
         public String toString()
         {
            return "Scheme.matches(\"" + pattern + "\")";
         }
      };
   }

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

      if (event instanceof HttpOutboundServletRewrite)
      {
         scheme = event.getAddress().getScheme();
         if (scheme == null)
            scheme = event.getRequest().getScheme();
      }
      else
         scheme = event.getRequest().getScheme();

      return (scheme != null && expression.parse(scheme).submit(event, context));
   }

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

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

   @Override
   public void setParameterStore(ParameterStore store)
   {
      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;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.digester.Digester;
import org.xml.sax.SAXException;

import com.ocpsoft.pretty.faces.annotation.URLAction.PhaseId;
import com.ocpsoft.pretty.faces.config.convert.CaseConverter;
import com.ocpsoft.pretty.faces.config.convert.PhaseIdConverter;
import com.ocpsoft.pretty.faces.config.convert.RedirectConverter;
import com.ocpsoft.pretty.faces.config.convert.TrailingSlashConverter;
import com.ocpsoft.pretty.faces.config.mapping.PathValidator;
import com.ocpsoft.pretty.faces.config.mapping.QueryParameter;
import com.ocpsoft.pretty.faces.config.mapping.UrlAction;
import com.ocpsoft.pretty.faces.config.mapping.UrlMapping;
import com.ocpsoft.pretty.faces.config.rewrite.Case;
import com.ocpsoft.pretty.faces.config.rewrite.Redirect;
import com.ocpsoft.pretty.faces.config.rewrite.RewriteRule;
import com.ocpsoft.pretty.faces.config.rewrite.TrailingSlash;

/**
 * Digester-based implementation of {@link PrettyConfigParser}.
 */
public class DigesterPrettyConfigParser implements PrettyConfigParser
{
   private final Converter caseConverter = new CaseConverter();
   private final Converter trailingSlashConverter = new TrailingSlashConverter();
   private final Converter phaseIdConverter = new PhaseIdConverter();
   private final Converter redirectConverter = new RedirectConverter();

   public void parse(final PrettyConfigBuilder builder, final InputStream resource) throws IOException, SAXException
   {
      if (builder == null)
      {
         throw new IllegalArgumentException("Builder must not be null.");
      }
      if (resource == null)
      {
         throw new IllegalArgumentException("Input stream must not be null.");
      }
      final Digester digester = configureDigester(new Digester());

      ConvertUtils.register(caseConverter, Case.class);
      ConvertUtils.register(trailingSlashConverter, TrailingSlash.class);
      ConvertUtils.register(phaseIdConverter, PhaseId.class);
      ConvertUtils.register(redirectConverter, Redirect.class);

      digester.push(builder);
      digester.parse(resource);
   }

   /**
    * Configure the digester. Assume that the builder object will be pushed
    * after this method is called.
    */
   private Digester configureDigester(final Digester digester)
   {

      /*
       * We use the context class loader to resolve classes. This fixes
       * ClassNotFoundExceptions on Geronimo.
       */
      digester.setUseContextClassLoader(true);

      /*
       * Parse RewriteRules
       */
      digester.addObjectCreate("pretty-config/rewrite", RewriteRule.class);
      digester.addSetProperties("pretty-config/rewrite");
      digester.addSetNext("pretty-config/rewrite", "addRewriteRule");

      /*
       * Create Mapping Object
       */
      digester.addObjectCreate("pretty-config/url-mapping", UrlMapping.class);
      digester.addSetProperties("pretty-config/url-mapping");
      digester.addCallMethod("pretty-config/url-mapping/pattern", "setPattern", 1);
      digester.addCallParam("pretty-config/url-mapping/pattern", 0, "value");
      digester.addCallMethod("pretty-config/url-mapping/pattern", "setPattern", 0);

      /*
       * Parse View Id
       */
      digester.addCallMethod("pretty-config/url-mapping/view-id", "setViewId", 1);
      digester.addCallParam("pretty-config/url-mapping/view-id", 0, "value");
      digester.addCallMethod("pretty-config/url-mapping/view-id", "setViewId", 0);

      /*
       * Parse Path Validators
       */
      digester.addObjectCreate("pretty-config/url-mapping/pattern/validate", PathValidator.class);
      digester.addSetProperties("pretty-config/url-mapping/pattern/validate");
      digester.addSetNext("pretty-config/url-mapping/pattern/validate", "addPathValidator");

      /*
       * Parse Query Params
       */
      digester.addObjectCreate("pretty-config/url-mapping/query-param", QueryParameter.class);
      digester.addSetProperties("pretty-config/url-mapping/query-param");
      digester.addCallMethod("pretty-config/url-mapping/query-param", "setExpression", 0);
      digester.addSetNext("pretty-config/url-mapping/query-param", "addQueryParam");

      /*
       * Parse Action Methods
       */
      digester.addObjectCreate("pretty-config/url-mapping/action", UrlAction.class);
      digester.addSetProperties("pretty-config/url-mapping/action");
      digester.addCallMethod("pretty-config/url-mapping/action", "setAction", 0);
      digester.addSetNext("pretty-config/url-mapping/action", "addAction");

      /*
       * Add Mapping to Builder
       */
      digester.addSetNext("pretty-config/url-mapping", "addMapping");
      return digester;
   }
}


/*
 * 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.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.InboundRewrite;
import org.ocpsoft.rewrite.event.OutboundRewrite;
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;
import org.ocpsoft.rewrite.servlet.event.InboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpInboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.util.Transpositions;
import org.ocpsoft.urlbuilder.Address;
import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * An {@link Operation} responsible for substituting an inbound or outbound {@link Address} with a replacement. For
 * {@link InboundRewrite} events, this calls {@link InboundServletRewrite#forward(String)}, and for
 * {@link OutboundRewrite} events, this calls {@link HttpOutboundServletRewrite#setOutboundAddress(String)}
 *
 * @author Lincoln Baxter, III
 */
public abstract class Substitute extends HttpOperation implements Parameterized
{
   private final ParameterizedPatternParser location;

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

   /**
    * Substitute the current {@link Address} with the given location.
    *

    * The given location may be parameterized:
    *

    * INBOUND:

    *
    *    /example/{param}.html

    *    /css/{value}.css

    *    ...
    *
    *

    * OUTBOUND:

    *
    *    www.example.com/path/file.html

    *    www.example.com/path/{resource}.html

    *    ...
    *
    *

    *
    * @param location {@link ParameterizedPattern} specifying the new {@link Address}.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static Substitute with(final String location)
   {
      return new Substitute(location) {
         @Override
         public String toString()
         {
            return "Substitute.with(\"" + location + "\")";
         }
      };
   }

   @Override
   public void performHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      if (event instanceof HttpInboundServletRewrite)
      {
         String target = location.getBuilder().build(event, context, Transpositions.encodePath());
         ((HttpInboundServletRewrite) event).forward(target);
      }
      else if (event instanceof HttpOutboundServletRewrite)
      {
         ParameterizedPatternBuilder builder = location.getBuilder();

         String target = builder.build(event, context, Transpositions.encodePath());
         if (((HttpOutboundServletRewrite) event).getOutboundAddress().getPath().startsWith(event.getContextPath())
                  && target.startsWith("/")
                  && !target.startsWith("//")
                  && !target.startsWith(event.getContextPath()))
         {
            target = event.getContextPath() + target;
         }
         ((HttpOutboundServletRewrite) event).setOutboundAddress(AddressBuilder.create(target));
      }
   }

   /**
    * Get the underlying {@link ParameterizedPatternParser} for this {@link Substitute}.
    */
   public ParameterizedPatternParser getExpression()
   {
      return location;
   }

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

   @Override
   public void setParameterStore(ParameterStore store)
   {
      location.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 org.ocpsoft.rewrite.annotation.scan;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import javax.servlet.ServletContext;

import org.ocpsoft.rewrite.annotation.api.ClassVisitor;
import org.ocpsoft.rewrite.annotation.spi.ClassFinder;

/**
 * Implementation of {@link ClassFinder} that searches for classes in the JAR archives found in the
 * /WEB-INF/lib/ directory of a web application.
 *
 * @author Christian Kaltepoth
 */
public class WebLibFinder extends AbstractClassFinder
{

   /**
    * The web application directory containing the JAR files
    */
   private final static String LIB_FOLDER = "/WEB-INF/lib/";

   /**
    * Initialization
    */
   public WebLibFinder(ServletContext servletContext, ClassLoader classLoader, PackageFilter packageFilter,
            ByteCodeFilter byteCodeFilter)
   {
      super(servletContext, classLoader, packageFilter, byteCodeFilter);
   }

   @Override
   public void findClasses(ClassVisitor visitor)
   {

      // catch MalformedURLException
      try
      {

         // get URL of the lib folder
         URL libFolderUrl = servletContext.getResource(LIB_FOLDER);

         // abort on missing lib folder
         if (libFolderUrl == null)
         {
            log.warn("Cannot find " + LIB_FOLDER + " folder!");
            return;
         }

         // call getResourcePaths to get directory entries
         Set<?> paths = servletContext.getResourcePaths(LIB_FOLDER);

         // loop over all entries of the directory
         for (Object relativePath : paths)
         {

            // get full URL of the current directory entry
            URL entryUrl = servletContext.getResource(relativePath.toString());

            // we are only interested in JAR files
            if (entryUrl.getPath().endsWith(".jar"))
            {
               processJarFile(entryUrl, visitor);
            }

         }

      }
      catch (MalformedURLException e)
      {
         throw new IllegalStateException("Invalid URL: " + e.getMessage(), e);
      }
   }

   /**
    * Process a single JAR file in the /WEB-INF/lib/ directory.
    *
    * @param jarUrl The URL of the JAR file
    * @param visitor The visitor to call on classes
    */
   private void processJarFile(URL jarUrl, ClassVisitor visitor)
   {

      // log file name on debug lvel
      if (log.isDebugEnabled())
      {
         log.debug("Processing JAR file: " + jarUrl.toString());
      }

      // Use a JarInputStream to read the archive
      JarInputStream jarStream = null;

      // catch any type of IOException
      try
      {

         // open the JAR stream
         jarStream = new JarInputStream(jarUrl.openStream());

         // Loop over all entries of the archive
         JarEntry jarEntry = null;
         while ((jarEntry = jarStream.getNextJarEntry()) != null)
         {

            // We are only interested in java class files
            if (jarEntry.getName().endsWith(".class"))
            {

               // generate FQCN from entry
               String className = getClassName(jarEntry.getName());

               // check name against PackageFilter
               if (mustProcessClass(className))
               {

                  // analyze this class
                  processClass(className, jarStream, visitor);

               }

            }

         }

      }
      catch (IOException e)
      {
         log.error("Failed to read JAR file: " + jarUrl.toString(), e);
      }
      finally
      {
         // Close the stream if it has been opened
         if (jarStream != null)
         {
            try
            {
               jarStream.close();
            }
            catch (IOException e)
            {
               // ignore IO failures on close
            }
         }
      }

   }

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

}


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

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ocpsoft.rewrite.annotation.api.ClassVisitor;

import com.ocpsoft.pretty.faces.annotation.URLAction;
import com.ocpsoft.pretty.faces.annotation.URLAction.PhaseId;
import com.ocpsoft.pretty.faces.annotation.URLActions;
import com.ocpsoft.pretty.faces.annotation.URLBeanName;
import com.ocpsoft.pretty.faces.annotation.URLMapping;
import com.ocpsoft.pretty.faces.annotation.URLMappings;
import com.ocpsoft.pretty.faces.annotation.URLQueryParameter;
import com.ocpsoft.pretty.faces.annotation.URLValidator;
import com.ocpsoft.pretty.faces.config.PrettyConfigBuilder;
import com.ocpsoft.pretty.faces.config.mapping.PathValidator;
import com.ocpsoft.pretty.faces.config.mapping.QueryParameter;
import com.ocpsoft.pretty.faces.config.mapping.UrlAction;
import com.ocpsoft.pretty.faces.config.mapping.UrlMapping;
import com.ocpsoft.pretty.faces.el.ConstantExpression;
import com.ocpsoft.pretty.faces.el.LazyBeanNameFinder;
import com.ocpsoft.pretty.faces.el.LazyExpression;
import com.ocpsoft.pretty.faces.el.PrettyExpression;

public class PrettyAnnotationHandler implements ClassVisitor
{

   /**
    * The logger
    */
   private static final Log log = LogFactory.getLog(PrettyAnnotationHandler.class);

   /**
    * A map assigning mapping IDs to {@link UrlMapping} instances
    */
   private final Map<String, UrlMapping> urlMappings = new LinkedHashMap<String, UrlMapping>();

   /**
    * A map to resolve the bean name for a {@link Class} object
    */
   private final Map<Class<?>, String> beanNameMap = new LinkedHashMap<Class<?>, String>();

   /**
    * The {@link ActionSpec} objects generated from annotation scanning
    */
   private final List<ActionSpec> urlActions = new ArrayList<ActionSpec>();

   /**
    * The {@link QueryParamSpec} objects generated from annotation scanning
    */
   private final List<QueryParamSpec> queryParamList = new ArrayList<QueryParamSpec>();

   /**
    * Reference to the {@link LazyBeanNameFinder}
    */
   private final LazyBeanNameFinder beanNameFinder;

   /**
    * Constructor
    */
   public PrettyAnnotationHandler(LazyBeanNameFinder beanNameFinder)
   {
      this.beanNameFinder = beanNameFinder;
   }

   /**
    * This method scans the supplied class for PrettyFaces annotations. The method must be called for every class that
    * should be scanner before finally calling {@link #build(PrettyConfigBuilder)}.
    *
    * @param clazz The class to scan
    */
   @Override
   public void visit(Class<?> clazz)
   {

      // log class name on trace level
      if (log.isTraceEnabled())
      {
         log.trace("Analyzing class: " + clazz.getName());
      }

      try
      {

         // scan for PrettyAnnotation class
         // returns the mapping ID, if an annotation was found
         String[] classMappingIds = processClassMappingAnnotations(clazz);

         // scan for PrettyBean annotation
         processPrettyBeanAnnotation(clazz);

         // process annotations on public methods
         for (Method method : clazz.getMethods())
         {
            processMethodAnnotations(method, classMappingIds);
         }

         // loop over fields to find URLQueryParameter annotations
         for (Field field : clazz.getDeclaredFields())
         {
            processFieldAnnotations(field, classMappingIds);
         }

      }
      catch (NoClassDefFoundError e)
      {
         // reference to another class unknown to the classloader
         log.debug("Unable to process class '" + clazz.getName() + "': " + e.toString());
      }

   }

   /**
    * Checks for PrettyFaces mapping annotations on a single class
    *
    * @param clazz Class to scan
    * @return The IDs of the mappings found on the class
    */
   public String[] processClassMappingAnnotations(Class<?> clazz)
   {

      // list of all mapping IDs found on the class
      List<String> classMappingIds = new ArrayList<String>();

      // get reference to @URLMapping annotation
      URLMapping mappingAnnotation = (URLMapping) clazz.getAnnotation(URLMapping.class);

      // process annotation if it exists
      if (mappingAnnotation != null)
      {
         String mappingId = processPrettyMappingAnnotation(clazz, mappingAnnotation);
         classMappingIds.add(mappingId);
      }

      // container annotation
      URLMappings mappingsAnnotation = (URLMappings) clazz.getAnnotation(URLMappings.class);

      if (mappingsAnnotation != null)
      {

         // process all contained @URLMapping annotations
         for (URLMapping child : mappingsAnnotation.mappings())
         {
            String mappingId = processPrettyMappingAnnotation(clazz, child);
            classMappingIds.add(mappingId);
         }

      }

      // return list of mappings found
      return classMappingIds.toArray(new String[classMappingIds.size()]);

   }

   /**
    * Process a single {@link URLMapping} annotation.
    *
    * @param clazz The class that the annotation was found on
    * @param mappingAnnotation The annotation to process
    * @return The mapping ID of the mapping found
    */
   private String processPrettyMappingAnnotation(Class<?> clazz, URLMapping mappingAnnotation)
   {

      // log class name
      if (log.isTraceEnabled())
      {
         log.trace("Found @URLMapping annotation on class: " + clazz.getName());
      }

      // create UrlMapping from annotation
      UrlMapping mapping = new UrlMapping();
      mapping.setId(mappingAnnotation.id());
      mapping.setParentId(mappingAnnotation.parentId());
      mapping.setPattern(mappingAnnotation.pattern());
      mapping.setViewId(mappingAnnotation.viewId());
      mapping.setOutbound(mappingAnnotation.outbound());
      mapping.setOnPostback(mappingAnnotation.onPostback());

      // register mapping
      Object existingMapping = urlMappings.put(mapping.getId(), mapping);

      // fail if a mapping with this ID already existed
      if (existingMapping != null)
      {
         throw new IllegalArgumentException("Duplicated mapping id: " + mapping.getId());
      }

      // At bean name to lookup map if it has been specified
      if ((mappingAnnotation.beanName() != null) && (mappingAnnotation.beanName().length() > 0))
      {
         beanNameMap.put(clazz, mappingAnnotation.beanName());
      }

      // process validations
      for (URLValidator validationAnnotation : mappingAnnotation.validation())
      {

         // index attribute is required in this case
         if (validationAnnotation.index() < 0)
         {
            throw new IllegalArgumentException(
                     "Please set the index of the path parameter you want to validate with the @URLValidator specified on mapping: "
                              + mapping.getId());
         }

         // prepare PathValidator
         PathValidator pathValidator = new PathValidator();
         pathValidator.setIndex(validationAnnotation.index());
         pathValidator.setOnError(validationAnnotation.onError());
         pathValidator.setValidatorIds(join(validationAnnotation.validatorIds(), " "));

         // optional validator method
         if (!isBlank(validationAnnotation.validator()))
         {
            pathValidator.setValidatorExpression(new ConstantExpression(validationAnnotation.validator()));
         }

         // add PathValidator to the mapping
         mapping.getPathValidators().add(pathValidator);

      }

      // return mapping id
      return mapping.getId().trim();

   }

   /**
    * Checks the class for a {@link URLBeanName} annotation.
    *
    * @param clazz Class to scan
    */
   private void processPrettyBeanAnnotation(Class<?> clazz)
   {

      // get reference to @URLMapping annotation
      URLBeanName prettyBean = (URLBeanName) clazz.getAnnotation(URLBeanName.class);

      // process annotation if it exists
      if (prettyBean != null)
      {

         // log class name
         if (log.isTraceEnabled())
         {
            log.trace("Found @URLBeanName annotation on class: " + clazz.getName());
         }

         // add bean to map
         beanNameMap.put(clazz, prettyBean.value());

      }

   }

   /**
    * Searches for {@link URLAction} or {@link URLActions} annotations on a method.
    *
    * @param method Method to scan
    * @param classMappingIds The mapping IDs of the class this method belongs to
    */
   private void processMethodAnnotations(Method method, String[] classMappingIds)
   {

      // is there a @URLAction annotation on the class?
      URLAction actionAnnotation = method.getAnnotation(URLAction.class);
      if (actionAnnotation != null)
      {
         processPrettyActionAnnotation(actionAnnotation, method, classMappingIds);
      }

      // is there a @URLAction container annotation on the class?
      URLActions actionsAnnotation = method.getAnnotation(URLActions.class);
      if (actionsAnnotation != null)
      {
         // process all @URLAction annotations
         for (URLAction child : actionsAnnotation.actions())
         {
            processPrettyActionAnnotation(child, method, classMappingIds);
         }
      }
   }

   /**
    * Searches for {@link URLQueryParameter} annotations on a single field.
    *
    * @param field Field to scan
    * @param classMappingIds The mapping IDs of the class this method belongs to
    */
   private void processFieldAnnotations(Field field, String[] classMappingIds)
   {
      // Is there a @URLQueryParameter annotation?
      URLQueryParameter queryParamAnnotation = field.getAnnotation(URLQueryParameter.class);

      if (queryParamAnnotation != null)
      {

         // create a QueryParamSpec from the annotation
         QueryParamSpec queryParam = new QueryParamSpec();
         queryParam.setFieldName(field.getName());
         queryParam.setOwnerClass(field.getDeclaringClass());
         queryParam.setName(queryParamAnnotation.value());
         queryParam.setOnPostback(queryParamAnnotation.onPostback());

         // check which mapping the action belongs to
         if (!isBlank(queryParamAnnotation.mappingId()))
         {
            // action belongs to the mapping mentioned with mappingId attribute
            queryParam.setMappingIds(new String[] { queryParamAnnotation.mappingId().trim() });
         }
         else if (classMappingIds != null && classMappingIds.length > 0)
         {
            // use the mappings found on the class
            queryParam.setMappingIds(classMappingIds);
         }
         else
         {
            throw new IllegalArgumentException("Unable to find a suitable mapping "
                     + "for the query-parameter definied on field '" + field.getName() + "' in class '"
                     + field.getDeclaringClass().getName() + "'. Either place a @URLMapping annotation on the "
                     + "class or reference a foreign mapping using the 'mappingId' attribute.");
         }

         // check if there is also a validation annotation placed on the field
         URLValidator validationAnnotation = field.getAnnotation(URLValidator.class);

         // check if annotation has been found
         if (validationAnnotation != null)
         {

            // set validation options on the QueryParamSpec object
            queryParam.setValidatorIds(validationAnnotation.validatorIds());
            queryParam.setOnError(validationAnnotation.onError());
            queryParam.setValidator(validationAnnotation.validator());

         }

         // add the new spec object to the list of specs
         queryParamList.add(queryParam);

      }

   }

   /**
    * Creates a {@link UrlAction} object from the supplied {@link URLAction} annotation
    *
    * @param actionAnnotation The annotation
    * @param method The method that was annotated
    * @param classMappingIds the mapping IDs of the current class
    */
   private void processPrettyActionAnnotation(URLAction actionAnnotation, Method method, String[] classMappingIds)
   {

      // Create ActionSpec
      ActionSpec actionSpec = new ActionSpec();
      actionSpec.setMethod(method);
      actionSpec.setOnPostback(actionAnnotation.onPostback());
      actionSpec.setInheritable(actionAnnotation.inheritable());
      actionSpec.setPhaseId(actionAnnotation.phaseId());

      // check which mapping the action belongs to
      if (!isBlank(actionAnnotation.mappingId()))
      {
         // action belongs to the mapping mentioned with mappingId attribute
         actionSpec.setMappingIds(new String[] { actionAnnotation.mappingId().trim() });
      }
      else if (classMappingIds != null && classMappingIds.length > 0)
      {
         // use the mapping found on the class
         actionSpec.setMappingIds(classMappingIds);
      }
      else
      {
         // No mapping found... throw an exception..
         throw new IllegalArgumentException("Unable to find a suitable mapping "
                  + "for the action definied on method '" + method.getName() + "' in class '"
                  + method.getDeclaringClass().getName() + "'. Either place a @URLMapping annotation on the "
                  + "class or reference a foreign mapping using the 'mappingId' attribute.");
      }

      // add action to list of actions
      urlActions.add(actionSpec);

   }

   /**
    * Returns true for "blank" strings.
    *
    * @param str Input string
    * @return true if string is null or trimmed value is empty
    */
   private static boolean isBlank(String str)
   {
      return (str == null) || (str.trim().length() == 0);
   }

   /**
    * This methods adds all mappings found to the supplied {@link PrettyConfigBuilder}. It should be called after all
    * classes has been scanned via {@link #processClass(Class)}.
    *
    * @param builder The builder to add the mappings to
    */
   public void build(PrettyConfigBuilder builder)
   {

      // process all actions found
      for (ActionSpec actionSpec : urlActions)
      {

         // create an action for each referenced mapping
         for (String mappingId : actionSpec.getMappingIds())
         {

            // Get the mapping references by the action
            UrlMapping mapping = urlMappings.get(mappingId);

            /*
             * Fail for unresolved mappings. This may happen when the user places
             * invalid mapping IDs in the mappingId attribute of
             *
             * @URLAction or @URLQueryParameter
             */
            if (mapping == null)
            {
               throw new IllegalArgumentException("Unable to find the mapping '" + mappingId
                        + "' referenced at method '" + actionSpec.getMethod().getName() + "' in class '"
                        + actionSpec.getMethod().getDeclaringClass().getName() + "'.");
            }

            // build UrlMapping
            UrlAction urlAction = new UrlAction();
            urlAction.setPhaseId(actionSpec.getPhaseId());
            urlAction.setOnPostback(actionSpec.isOnPostback());
            urlAction.setInheritable(actionSpec.isInheritable());

            // try to get bean name
            Class<?> clazz = actionSpec.getMethod().getDeclaringClass();

            // build expression
            PrettyExpression expression = buildPrettyExpression(clazz, actionSpec.getMethod().getName());
            urlAction.setAction(expression);

            // trace
            if (log.isTraceEnabled())
            {
               log.trace("Adding action expression '" + urlAction.getAction() + "' to mapping: " + mapping.getId());
            }

            // register this action
            mapping.addAction(urlAction);

         }
      }

      for (QueryParamSpec queryParamSpec : queryParamList)
      {

         // create a query param for each referenced mapping
         for (String mappingId : queryParamSpec.getMappingIds())
         {

            // Get the mapping references by the query param
            UrlMapping mapping = urlMappings.get(mappingId);

            // fail for unresolved mappings
            if (mapping == null)
            {
               throw new IllegalArgumentException("Unable to find the mapping '" + mappingId
                        + "' referenced at field '" + queryParamSpec.getFieldName() + "' in class '"
                        + queryParamSpec.getOwnerClass().getName() + "'.");
            }

            // build UrlMapping
            QueryParameter queryParam = new QueryParameter();
            queryParam.setName(queryParamSpec.getName());
            queryParam.setOnError(queryParamSpec.getOnError());
            queryParam.setValidatorIds(join(queryParamSpec.getValidatorIds(), " "));
            queryParam.setOnPostback(queryParamSpec.isOnPostback());

            // optional validator method
            if (!isBlank(queryParamSpec.getValidator()))
            {
               queryParam.setValidatorExpression(new ConstantExpression(queryParamSpec.getValidator()));
            }

            // try to get bean name
            Class<?> clazz = queryParamSpec.getOwnerClass();

            // build expression
            PrettyExpression expression = buildPrettyExpression(clazz, queryParamSpec.getFieldName());
            queryParam.setExpression(expression);

            // trace
            if (log.isTraceEnabled())
            {
               log.trace("Registered query-param '" + queryParam.getName() + "' to '" + expression + "' in mapping: "
                        + mapping.getId());
            }

            // register this action
            mapping.addQueryParam(queryParam);

         }

      }

      // finally register all mappings
      for (UrlMapping mapping : urlMappings.values())
      {
         builder.addMapping(mapping);
      }
   }

   /**
    * Creates a {@link PrettyExpression} for a class and component. This method may return a {@link ConstantExpression}
    * or a {@link LazyExpression}.
    *
    * @param clazz The class of the bean
    * @param component the component (property or method name)
    * @return The expression
    */
   private PrettyExpression buildPrettyExpression(Class<?> clazz, String component)
   {

      if (log.isTraceEnabled())
      {
         log.trace("Searching name of bean: " + clazz.getName());
      }

      // get name from internal map build from @URLBeanName annotations and
      // previously resolved names
      String beanName = beanNameMap.get(clazz);

      // return a constant expression
      if (beanName != null)
      {
         if (log.isTraceEnabled())
         {
            log.trace("Got bean name from @URLBeanName annotation: " + beanName);
         }

         return new ConstantExpression("#{" + beanName + "." + component + "}");
      }

      // build a lazy expression
      else
      {

         if (log.isTraceEnabled())
         {
            log.trace("Name of bean not found. Building lazy expression for: " + clazz.getName());
         }

         return new LazyExpression(beanNameFinder, clazz, component);

      }

   }

   /**
    * Joins the list of values.
    *
    * @param values values to join
    * @param separator the separator to use
    * @return joined list of values
    */
   private static String join(String[] values, String separator)
   {
      StringBuilder result = new StringBuilder();
      if (values != null)
      {
         for (int i = 0; i < values.length; i++)
         {
            if (i > 0)
            {
               result.append(separator);
            }
            result.append(values[i]);
         }
      }
      return result.toString();
   }

   /**
    * Internal class to hold parameters of a {@link URLAction} annotation.
    *
    * @author Christian Kaltepoth
    */
   private static class ActionSpec
   {

      private Method method;
      private boolean onPostback;
      private PhaseId phaseId;
      private String[] mappingIds;
      private boolean inheritable;

      public boolean isOnPostback()
      {
         return onPostback;
      }

      public void setOnPostback(boolean onPostback)
      {
         this.onPostback = onPostback;
      }

      public PhaseId getPhaseId()
      {
         return phaseId;
      }

      public void setPhaseId(PhaseId phaseId)
      {
         this.phaseId = phaseId;
      }

      public Method getMethod()
      {
         return method;
      }

      public void setMethod(Method method)
      {
         this.method = method;
      }

      public String[] getMappingIds()
      {
         return mappingIds;
      }

      public void setMappingIds(String[] mappingIds)
      {
         this.mappingIds = mappingIds;
      }

      public boolean isInheritable()
      {
         return inheritable;
      }

      public void setInheritable(boolean inheritable)
      {
         this.inheritable = inheritable;
      }

   }

   /**
    * Internal class to hold parameters of a Pretty annotation.
    *
    * @author Christian Kaltepoth
    */
   private static class QueryParamSpec
   {
      private String fieldName;
      private Class<?> ownerClass;
      private String[] mappingIds;
      private String name;
      private String onError;
      private String[] validatorIds = {};
      private String validator;
      private boolean onPostback;

      public String getValidator()
      {
         return validator;
      }

      public void setValidator(String validator)
      {
         this.validator = validator;
      }

      public String getName()
      {
         return name;
      }

      public void setName(String name)
      {
         this.name = name;
      }

      public String getFieldName()
      {
         return fieldName;
      }

      public void setFieldName(String fieldName)
      {
         this.fieldName = fieldName;
      }

      public Class<?> getOwnerClass()
      {
         return ownerClass;
      }

      public void setOwnerClass(Class<?> ownerClass)
      {
         this.ownerClass = ownerClass;
      }

      public String getOnError()
      {
         return onError;
      }

      public void setOnError(String onError)
      {
         this.onError = onError;
      }

      public String[] getValidatorIds()
      {
         return validatorIds;
      }

      public void setValidatorIds(String[] validatorIds)
      {
         this.validatorIds = validatorIds;
      }

      public boolean isOnPostback()
      {
         return onPostback;
      }

      public void setOnPostback(boolean onPostback)
      {
         this.onPostback = onPostback;
      }

      public String[] getMappingIds()
      {
         return mappingIds;
      }

      public void setMappingIds(String[] mappingIds)
      {
         this.mappingIds = mappingIds;
      }
   }

}


/*
 * 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 path.
 *
 * @author Lincoln Baxter, III
 */
public class AddressBuilderPath implements BuildableAddress
{
   private AddressBuilder parent;

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

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

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

   /**
    * 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);
   }

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

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


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

import java.net.MalformedURLException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.servlet.Servlet;
import javax.servlet.ServletContext;

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.config.Condition;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
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.ServletRegistration;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.spi.ServletRegistrationProvider;
import org.ocpsoft.rewrite.util.Transpositions;
import org.ocpsoft.urlbuilder.Address;

/**
 * A {@link Condition} responsible for comparing the current {@link Address} to Servlet Mappings defined in the current
 * {@link ServletContext}.
 *
 * @author Lincoln Baxter, III
 */
public abstract class ServletMapping extends HttpCondition implements Parameterized
{
   private static final Logger log = Logger.getLogger(Resource.class);

   private final RegexParameterizedPatternBuilder resource;

   private List<ServletRegistrationProvider> servletRegistrationProviders = null;

   private ServletMapping(final String resource)
   {
      this.resource = new RegexParameterizedPatternBuilder(resource);
   }

   /**
    * Create a {@link Condition} that returns true if the given resource is mapped by any {@link Servlet}
    * instances registered within the current application, and returns false if no {@link Servlet} will
    * handle the specified resource pattern.
    *
    *

    * The given resource path may be parameterized:
    *

    *
    *    /example/{param}.html

    *    /css/{value}.css

    *    ...
    *
    *

    *
    * @param location {@link ParameterizedPattern} specifying the {@link Address} of the internal resource.
    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)}
    */
   public static ServletMapping includes(final String resource)
   {
      return new ServletMapping(resource) {
         @Override
         public String toString()
         {
            return "ServletMapping.includes(\"" + resource + "\")";
         }
      };
   }

   @Override
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      if (resource != null && resource.isParameterComplete(event, context))
      {
         String path = resource.build(event, context, Transpositions.encodePath());
         try
         {
            for (ServletRegistration registration : getServletRegistration(event.getServletContext()))
            {
               Collection<String> mappings = registration.getMappings();

               for (String mapping : mappings)
               {
                  if (path.startsWith("/") && !mapping.startsWith("/"))
                  {
                     mapping = "/" + mapping;
                  }

                  if (mapping.contains("*"))
                  {
                     mapping = mapping.replaceAll("\\*", ".*");
                  }

                  if (path.matches(mapping))
                  {
                     return true;
                  }
               }
            }

            return event.getServletContext().getResource(path) != null;
         }
         catch (MalformedURLException e)
         {
            log.debug("Invalid file format [{}]", path);
         }
      }
      return false;
   }

   /**
    * Obtains the list of registered {@link Servlet} instances using the {@link ServletRegistrationProvider} SPI.
    */
   private List<ServletRegistration> getServletRegistration(ServletContext context)
   {
      for (ServletRegistrationProvider provider : getServletRegistrationProviders())
      {
         List<ServletRegistration> registrations = provider.getServletRegistrations(context);
         if (registrations != null)
         {
            return registrations;
         }
      }
      throw new IllegalStateException("Unable to find the Servlet registrations of the application");
   }

   /**
    * Returns the list of {@link ServletRegistrationProvider} implementations.
    */
   private List<ServletRegistrationProvider> getServletRegistrationProviders()
   {
      if (servletRegistrationProviders == null)
      {
         servletRegistrationProviders = Iterators.asList(
                  ServiceLoader.loadTypesafe(ServletRegistrationProvider.class).iterator());
         Collections.sort(servletRegistrationProviders, new WeightedComparator());
      }
      return servletRegistrationProviders;
   }

   /**
    * Return the underlying {@link ParameterizedPatternBuilder} for this {@link ServletMapping}.
    */
   public ParameterizedPatternBuilder getResourceExpression()
   {
      return resource;
   }

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

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