
org.ocpsoft.rewrite.servlet.config.rule.Join Maven / Gradle / Ivy
/*
* 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