All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ocpsoft.rewrite.servlet.config.rule.Join Maven / Gradle / Ivy

There is a newer version: 10.0.2.Final
Show newest version
/*
 * 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.rule;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.rewrite.bind.Binding;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.config.ConditionVisit;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.config.ParameterizedCallback;
import org.ocpsoft.rewrite.config.ParameterizedConditionVisitor;
import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.InboundRewrite;
import org.ocpsoft.rewrite.event.Rewrite;
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.servlet.config.DispatchType;
import org.ocpsoft.rewrite.servlet.config.Forward;
import org.ocpsoft.rewrite.servlet.config.Path;
import org.ocpsoft.rewrite.servlet.config.Query;
import org.ocpsoft.rewrite.servlet.config.Redirect;
import org.ocpsoft.rewrite.servlet.config.Substitute;
import org.ocpsoft.rewrite.servlet.config.bind.RequestBinding;
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.servlet.spi.RequestParameterProvider;
import org.ocpsoft.urlbuilder.Address;
import org.ocpsoft.urlbuilder.AddressBuilder;
import org.ocpsoft.urlbuilder.AddressBuilderBase;

/**
 * {@link Rule} that creates a bi-directional rewrite rule between an externally facing {@link Address} and an internal
 * server resource {@link Address} for the purposes of changing the {@link Address} with which the internal server
 * resource is accessible.
 * 
 * @author Lincoln Baxter, III
 */
public class Join implements Rule, JoinPath, Parameterized
{
   private static final String JOIN_DISABLED_KEY = Join.class.getName() + "_DISABLED";

   private static final String CURRENT_JOIN = Join.class.getName() + "_current";

   private String id;

   private final String requestPattern;
   private String resourcePattern;
   private final Path requestPath;
   private Path resourcePath;

   private boolean inboundCorrection = false;
   private boolean chainingDisabled = true;
   private boolean bindingEnabled = true;

   private Set pathRequestParameters;

   private ConditionBuilder outboundConditionCache;

   private ParameterStore store;

   protected Join(final String pattern, boolean requestBinding)
   {
      this.requestPattern = pattern;
      this.requestPath = Path.matches(pattern);
      if (requestBinding)
         requestPath.withRequestBinding();
      this.bindingEnabled = requestBinding;
   }

   /**
    * Create a {@link Rule} specifying the inbound request {@link Address} to which this {@link Join} will apply. Any
    * {@link Parameter} instances defined in the given pattern will be bound by default to the
    * {@link HttpServletRequest#getParameterMap()} via the {@link RequestParameterProvider} SPI.
    * 

* To disable {@link RequestBinding} parameter {@link Binding}, instead use {@link #pathNonBinding(String)}. *

* The given pattern may be parameterized: *

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

* * @param pattern {@link ParameterizedPattern} matching the requested path. * * @see ConfigurationRuleParameterBuilder#where(String) */ public static JoinPath path(final String pattern) { return new Join(pattern, true); } /** * Create a {@link Rule} specifying the inbound request {@link Address} to which this {@link Join} will apply. Any * {@link Parameter} instances defined in the given pattern will NOT be bound by default to the * {@link HttpServletRequest#getParameterMap()}. *

* To enable {@link RequestBinding} parameter {@link Binding}, instead use {@link #path(String)}. *

* The given pattern may be parameterized: *

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

* * @param pattern {@link ParameterizedPattern} matching the requested path. * * @see ConfigurationRuleParameterBuilder#where(String) */ public static JoinPath pathNonBinding(String pattern) { return new Join(pattern, false); } /** * Retrieve the {@link Join} that was invoked on the current {@link HttpServletRequest}; if no {@link Join} was * invoked, return null. */ public static Join getCurrentJoin(final HttpServletRequest request) { return (Join) request.getAttribute(CURRENT_JOIN); } @Override public Join to(final String resource) { if (this.resourcePattern != null) { throw new IllegalStateException("Cannot set resource path more than once."); } this.resourcePattern = resource; this.resourcePath = Path.matches(resource); Set parameters = getPathRequestParameters(); if (outboundConditionCache == null) { this.outboundConditionCache = resourcePath; for (String name : parameters) { Query parameter = Query.parameterExists(name); outboundConditionCache = outboundConditionCache.and(parameter); } } return this; } /** * Specifies that requests for the original internal resource path specified by {@link Join#to(String)} will be * redirected to the updated path specified by {@link Join#path(String)}. */ public Join withInboundCorrection() { this.inboundCorrection = true; return this; } /** * Enable the target of this {@link Join}, specified by {@link Join#to(String)}, to be intercepted by the * {@link Join#path(String)} of another {@link Join} instance. If not activated, subsequent matching {@link Join} * instances will not be evaluated on the current {@link InboundRewrite} instance. */ public Join withChaining() { this.chainingDisabled = false; return this; } @Override public boolean evaluate(final Rewrite event, final EvaluationContext context) { if (event instanceof HttpInboundServletRewrite) { if (!isChainingDisabled(event) && requestPath.evaluate(event, context)) { return true; } else if (inboundCorrection && resourcePath.andNot(DispatchType.isForward()).evaluate(event, context)) { Set parameters = getPathRequestParameters(); for (String param : parameters) { Query query = Query.parameterExists(param); query.setParameterStore(store); if (!query.evaluate(event, context)) { return false; } } Redirect redirect = Redirect.permanent(((HttpInboundServletRewrite) event).getContextPath() + requestPattern); redirect.setParameterStore(store); context.addPreOperation(redirect); return true; } } else if ((event instanceof HttpOutboundServletRewrite)) { if (outboundConditionCache.evaluate(event, context)) { return true; } } return false; } private boolean isChainingDisabled(Rewrite event) { return Boolean.TRUE.equals(event.getRewriteContext().get(JOIN_DISABLED_KEY)); } private Set getPathRequestParameters() { /* * For performance purposes - think and profile this if changed. */ if (pathRequestParameters == null) { Set nonQueryParameters = resourcePath.getRequiredParameterNames(); Set queryParameters = requestPath.getRequiredParameterNames(); queryParameters.removeAll(nonQueryParameters); pathRequestParameters = queryParameters; } return pathRequestParameters; } @Override public void perform(final Rewrite event, final EvaluationContext context) { if (event instanceof HttpInboundServletRewrite) { saveCurrentJoin(((HttpInboundServletRewrite) event).getRequest()); if (chainingDisabled) { event.getRewriteContext().put(JOIN_DISABLED_KEY, true); } Forward forward = Forward.to(resourcePattern); forward.setParameterStore(store); forward.perform(event, context); } else if (event instanceof HttpOutboundServletRewrite) { Address outboundAddress = ((HttpOutboundServletRewrite) event).getOutboundAddress(); /* * Remove known path parameters from query parameter list to be included in new URL */ Set pathParameters = getPathRequestParameters(); AddressBuilderBase queryBuilder = AddressBuilder.begin(); if (outboundAddress.isQuerySet()) { // Create a new query string without the parameters that were used in building the path Map> queryParameters = outboundAddress.getQueryParameters(); for (Entry> queryParameter : queryParameters.entrySet()) { String parameterName = queryParameter.getKey(); List parameterValues = queryParameter.getValue(); if (pathParameters.contains(parameterName)) { List newParameterValues = new ArrayList(parameterValues); newParameterValues.remove(0); if (!newParameterValues.isEmpty()) queryBuilder.query(parameterName, newParameterValues.toArray()); } else { queryBuilder.query(parameterName, parameterValues.toArray()); } } } Address queryResult = queryBuilder.buildLiteral(); Substitute substitute = Substitute.with(requestPattern + queryResult.toString()); substitute.setParameterStore(store); substitute.perform(event, context); Address rewrittenAddress = ((HttpOutboundServletRewrite) event).getOutboundAddress(); String rewrittenPath = rewrittenAddress.getPath(); String contextPath = ((HttpServletRewrite) event).getContextPath(); if (!contextPath.equals("/") && rewrittenPath.startsWith(contextPath)) rewrittenPath = rewrittenPath.substring(contextPath.length()); if (!outboundAddress.equals(rewrittenAddress) && !requestPath.getExpression().parse(rewrittenPath).submit(event, context)) { ((HttpOutboundServletRewrite) event).setOutboundAddress(rewrittenAddress); } else if (chainingDisabled) { ((HttpOutboundServletRewrite) event).handled(); } } } private void saveCurrentJoin(final HttpServletRequest request) { request.setAttribute(CURRENT_JOIN, this); } @Override public String getId() { return id; } @Override public String toString() { String result = "Join."; if (bindingEnabled) result += "path"; else result += "pathNonBinding"; result += "(\"" + requestPattern + "\").to(\"" + resourcePattern + "\")"; if (inboundCorrection) result += ".withInboundCorrection()"; if (!chainingDisabled) result += ".withChaning()"; return result; } @Override public Set getRequiredParameterNames() { final Set result = new LinkedHashSet(); result.addAll(requestPath.getRequiredParameterNames()); result.addAll(resourcePath.getRequiredParameterNames()); if (outboundConditionCache != null) { ParameterizedConditionVisitor visitor = new ParameterizedConditionVisitor(new ParameterizedCallback() { @Override public void call(Parameterized parameterized) { result.addAll(parameterized.getRequiredParameterNames()); } }); new ConditionVisit(outboundConditionCache).accept(visitor); } return result; } @Override public void setParameterStore(final ParameterStore store) { this.store = store; requestPath.setParameterStore(store); resourcePath.setParameterStore(store); if (outboundConditionCache != null) { ParameterizedConditionVisitor visitor = new ParameterizedConditionVisitor(new ParameterizedCallback() { @Override public void call(Parameterized parameterized) { parameterized.setParameterStore(store); } }); new ConditionVisit(outboundConditionCache).accept(visitor); } } }