FELIX-2774, FELIX-2797 & FELIX-4424:
- implement support for including and forwarding requests using the Servlet
RequestDispatcher, including several itests to verify behaviour [FELIX-2774];
- wrap HttpSessions in order to provide access to the correct ServletContext
[FELIX-2797];
- rudimentary fix for possible classloader leakage [FELIX-4424];
- prepared the Servlet- & FilterHandlers for named servlet/filters;
- several fixes to make the HTTP implementation more conform the Servlet 3.0
specification.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1567561 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java b/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java
index 36d21b3..05b93a7 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java
@@ -16,22 +16,22 @@
*/
package org.apache.felix.http.base.internal;
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestEvent;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletRequestAttributeEvent;
-import javax.servlet.ServletRequestEvent;
import org.apache.felix.http.base.internal.listener.ServletRequestAttributeListenerManager;
+import org.apache.felix.http.base.internal.listener.ServletRequestListenerManager;
-import java.io.IOException;
-
-public final class DispatcherServlet
- extends HttpServlet
+public final class DispatcherServlet extends HttpServlet
{
private final HttpServiceController controller;
@@ -41,8 +41,7 @@
}
@Override
- public void init(ServletConfig config)
- throws ServletException
+ public void init(ServletConfig config) throws ServletException
{
super.init(config);
this.controller.register(getServletContext());
@@ -56,11 +55,12 @@
}
@Override
- protected void service(HttpServletRequest req, HttpServletResponse res)
- throws ServletException, IOException
+ protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
+ ServletRequestListenerManager requestListener = this.controller.getRequestListener();
+
final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), req);
- this.controller.getRequestListener().requestInitialized(sre);
+ requestListener.requestInitialized(sre);
try
{
req = new AttributeEventRequest(getServletContext(), this.controller.getRequestAttributeListener(), req);
@@ -68,22 +68,20 @@
}
finally
{
- this.controller.getRequestListener().requestDestroyed(sre);
+ requestListener.requestDestroyed(sre);
}
}
private static class AttributeEventRequest extends HttpServletRequestWrapper
{
-
private final ServletContext servletContext;
- private final ServletRequestAttributeListenerManager requestAttributeListener;
+ private final ServletRequestAttributeListenerManager listener;
- public AttributeEventRequest(ServletContext servletContext,
- ServletRequestAttributeListenerManager requestAttributeListener, HttpServletRequest request)
+ public AttributeEventRequest(ServletContext servletContext, ServletRequestAttributeListenerManager requestAttributeListener, HttpServletRequest request)
{
super(request);
this.servletContext = servletContext;
- this.requestAttributeListener = requestAttributeListener;
+ this.listener = requestAttributeListener;
}
public void setAttribute(String name, Object value)
@@ -99,13 +97,11 @@
if (oldValue == null)
{
- requestAttributeListener.attributeAdded(new ServletRequestAttributeEvent(servletContext, this,
- name, value));
+ this.listener.attributeAdded(new ServletRequestAttributeEvent(this.servletContext, this, name, value));
}
else
{
- requestAttributeListener.attributeReplaced(new ServletRequestAttributeEvent(servletContext, this,
- name, oldValue));
+ this.listener.attributeReplaced(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
}
}
}
@@ -117,9 +113,14 @@
if (oldValue != null)
{
- requestAttributeListener.attributeRemoved(new ServletRequestAttributeEvent(servletContext, this, name,
- oldValue));
+ this.listener.attributeRemoved(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
}
}
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "->" + super.getRequest();
+ }
}
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java b/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
index dfab10a..34d5903 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
@@ -81,7 +81,7 @@
this.sessionListener = new HttpSessionListenerManager(bundleContext);
this.sessionAttributeListener = new HttpSessionAttributeListenerManager(bundleContext);
this.sharedContextAttributes = getBoolean(FELIX_HTTP_SHARED_SERVLET_CONTEXT_ATTRIBUTES);
- this.plugin = new HttpServicePlugin(bundleContext,registry);
+ this.plugin = new HttpServicePlugin(bundleContext, registry);
}
public Dispatcher getDispatcher()
@@ -119,7 +119,8 @@
this.serviceProps.clear();
this.serviceProps.putAll(props);
- if (this.serviceReg != null) {
+ if (this.serviceReg != null)
+ {
this.serviceReg.setProperties(this.serviceProps);
}
}
@@ -133,15 +134,16 @@
this.sessionAttributeListener.open();
this.plugin.register();
- HttpServiceFactory factory = new HttpServiceFactory(servletContext, this.registry,
- this.contextAttributeListener, this.sharedContextAttributes);
String[] ifaces = new String[] { HttpService.class.getName(), ExtHttpService.class.getName() };
+ HttpServiceFactory factory = new HttpServiceFactory(servletContext, this.registry, this.contextAttributeListener, this.sharedContextAttributes);
+
this.serviceReg = this.bundleContext.registerService(ifaces, factory, this.serviceProps);
}
public void unregister()
{
- if (this.serviceReg == null) {
+ if (this.serviceReg == null)
+ {
return;
}
@@ -152,10 +154,13 @@
this.requestAttributeListener.close();
this.plugin.unregister();
- try {
+ try
+ {
this.serviceReg.unregister();
this.registry.removeAll();
- } finally {
+ }
+ finally
+ {
this.serviceReg = null;
}
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java
index 56f67d7..fc7ec8b 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java
@@ -22,9 +22,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-public interface ExtServletContext
- extends ServletContext
+public interface ExtServletContext extends ServletContext
{
- public boolean handleSecurity(HttpServletRequest req, HttpServletResponse res)
- throws IOException;
+ boolean handleSecurity(HttpServletRequest req, HttpServletResponse res) throws IOException;
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java
index ca6d952..e0fa187 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java
@@ -48,7 +48,7 @@
import org.osgi.service.http.HttpContext;
@SuppressWarnings("deprecation")
-public final class ServletContextImpl implements ExtServletContext
+public class ServletContextImpl implements ExtServletContext
{
private final Bundle bundle;
private final ServletContext context;
@@ -65,6 +65,16 @@
this.attributes = sharedAttributes ? null : new ConcurrentHashMap<String, Object>();
}
+ protected ServletContextImpl(ExtServletContext delegate)
+ {
+ ServletContextImpl impl = (ServletContextImpl) delegate;
+ this.bundle = impl.bundle;
+ this.context = impl.context;
+ this.httpContext = impl.httpContext;
+ this.attributeListener = impl.attributeListener;
+ this.attributes = impl.attributes;
+ }
+
public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> type)
{
throw new UnsupportedOperationException();
@@ -229,7 +239,8 @@
public String getRealPath(String name)
{
URL url = getResource(normalizePath(name));
- if (url == null) {
+ if (url == null)
+ {
return null;
}
return url.toExternalForm();
@@ -354,7 +365,7 @@
if (oldValue != null)
{
- attributeListener.attributeRemoved(new ServletContextAttributeEvent(this, name, oldValue));
+ this.attributeListener.attributeRemoved(new ServletContextAttributeEvent(this, name, oldValue));
}
}
@@ -379,11 +390,11 @@
if (oldValue == null)
{
- attributeListener.attributeAdded(new ServletContextAttributeEvent(this, name, value));
+ this.attributeListener.attributeAdded(new ServletContextAttributeEvent(this, name, value));
}
else
{
- attributeListener.attributeReplaced(new ServletContextAttributeEvent(this, name, oldValue));
+ this.attributeListener.attributeReplaced(new ServletContextAttributeEvent(this, name, oldValue));
}
}
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java
index 2ab7919..9f6aec6 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java
@@ -16,8 +16,8 @@
*/
package org.apache.felix.http.base.internal.context;
-import java.util.HashMap;
import java.util.Map;
+import java.util.WeakHashMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeListener;
@@ -33,32 +33,35 @@
private final Map<HttpContext, ExtServletContext> contextMap;
private final boolean sharedAttributes;
- public ServletContextManager(Bundle bundle, ServletContext context,
- ServletContextAttributeListener attributeListener, boolean sharedAttributes)
+ public ServletContextManager(Bundle bundle, ServletContext context, ServletContextAttributeListener attributeListener, boolean sharedAttributes)
{
this.bundle = bundle;
this.context = context;
this.attributeListener = attributeListener;
- this.contextMap = new HashMap<HttpContext, ExtServletContext>();
+ // FELIX-4424 : avoid classloader leakage through HttpContext, for now this is sufficient,
+ // the real fix should be to remove ExtServletContext's when the usage count of HttpContext
+ // drops to zero.
+ this.contextMap = new WeakHashMap<HttpContext, ExtServletContext>();
this.sharedAttributes = sharedAttributes;
}
public ExtServletContext getServletContext(HttpContext httpContext)
{
- synchronized (this.contextMap) {
- ExtServletContext context = this.contextMap.get(httpContext);
- if (context == null) {
+ ExtServletContext context;
+ synchronized (this.contextMap)
+ {
+ context = this.contextMap.get(httpContext);
+ if (context == null)
+ {
context = addServletContext(httpContext);
}
-
- return context;
}
+ return context;
}
private ExtServletContext addServletContext(HttpContext httpContext)
{
- ExtServletContext context = new ServletContextImpl(this.bundle, this.context, httpContext, attributeListener,
- sharedAttributes);
+ ExtServletContext context = new ServletContextImpl(this.bundle, this.context, httpContext, this.attributeListener, this.sharedAttributes);
this.contextMap.put(httpContext, context);
return context;
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
index a7e6ba0..f48edf8 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
@@ -16,14 +16,21 @@
*/
package org.apache.felix.http.base.internal.dispatch;
-import org.apache.felix.http.base.internal.handler.HandlerRegistry;
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.servlet.ServletException;
-import java.io.IOException;
+
+import org.apache.felix.http.base.internal.handler.HandlerRegistry;
public final class Dispatcher
{
+ public static final String REQUEST_DISPATCHER_PROVIDER = "org.apache.felix.http.requestDispatcherProvider";
+
+ private static final FilterChain DEFAULT_CHAIN = new NotFoundFilterChain();
+
private final HandlerRegistry handlerRegistry;
public Dispatcher(HandlerRegistry handlerRegistry)
@@ -31,11 +38,19 @@
this.handlerRegistry = handlerRegistry;
}
- public void dispatch(HttpServletRequest req, HttpServletResponse res)
- throws ServletException, IOException
+ public void dispatch(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
ServletPipeline servletPipeline = new ServletPipeline(this.handlerRegistry.getServlets());
- FilterPipeline filterPipeline = new FilterPipeline(this.handlerRegistry.getFilters(), servletPipeline);
- filterPipeline.dispatch(req, res, new NotFoundFilterChain());
+ // Provides access to the correct request dispatcher...
+ req.setAttribute(REQUEST_DISPATCHER_PROVIDER, servletPipeline);
+
+ try
+ {
+ new FilterPipeline(this.handlerRegistry.getFilters(), servletPipeline).dispatch(req, res, DEFAULT_CHAIN);
+ }
+ finally
+ {
+ req.removeAttribute(REQUEST_DISPATCHER_PROVIDER);
+ }
}
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java
index 880b78c..c6a696a 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java
@@ -16,17 +16,45 @@
*/
package org.apache.felix.http.base.internal.dispatch;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.ServletException;
+import java.io.IOException;
+
import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
-import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
import org.apache.felix.http.base.internal.handler.FilterHandler;
public final class FilterPipeline
{
+ private static class FilterRequestWrapper extends HttpServletRequestWrapper
+ {
+ public FilterRequestWrapper(HttpServletRequest req)
+ {
+ super(req);
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ RequestDispatcherProvider provider = (RequestDispatcherProvider) getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+ RequestDispatcher dispatcher = null;
+ if (provider != null)
+ {
+ dispatcher = provider.getRequestDispatcher(path);
+ }
+ return (dispatcher != null) ? dispatcher : super.getRequestDispatcher(path);
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "->" + super.getRequest();
+ }
+ }
+
private final FilterHandler[] handlers;
private final ServletPipeline servletPipeline;
@@ -36,31 +64,15 @@
this.servletPipeline = servletPipeline;
}
- public void dispatch(HttpServletRequest req, HttpServletResponse res, FilterChain proceedingChain)
- throws ServletException, IOException
+ public void dispatch(HttpServletRequest req, HttpServletResponse res, FilterChain proceedingChain) throws ServletException, IOException
{
FilterChain chain = new InvocationFilterChain(this.handlers, this.servletPipeline, proceedingChain);
- if (this.servletPipeline.hasServletsMapped()) {
- req = new RequestWrapper(req);
+ if (this.servletPipeline.hasServletsMapped())
+ {
+ req = new FilterRequestWrapper(req);
}
chain.doFilter(req, res);
}
-
- private final class RequestWrapper
- extends HttpServletRequestWrapper
- {
- public RequestWrapper(HttpServletRequest req)
- {
- super(req);
- }
-
- @Override
- public RequestDispatcher getRequestDispatcher(String path)
- {
- final RequestDispatcher dispatcher = servletPipeline.getRequestDispatcher(path);
- return (null != dispatcher) ? dispatcher : super.getRequestDispatcher(path);
- }
- }
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
index 57fb49b..e8cc480 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
@@ -24,15 +24,12 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-public abstract class HttpFilterChain
- implements FilterChain
+public abstract class HttpFilterChain implements FilterChain
{
- public final void doFilter(ServletRequest req, ServletResponse res)
- throws IOException, ServletException
+ public final void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException
{
- doFilter((HttpServletRequest)req, (HttpServletResponse)res);
+ doFilter((HttpServletRequest) req, (HttpServletResponse) res);
}
- protected abstract void doFilter(HttpServletRequest req, HttpServletResponse res)
- throws IOException, ServletException;
+ protected abstract void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException;
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
index 0072a91..6bbf4f2 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
@@ -23,12 +23,11 @@
import java.io.IOException;
import org.apache.felix.http.base.internal.handler.FilterHandler;
-public final class InvocationFilterChain
- extends HttpFilterChain
+public final class InvocationFilterChain extends HttpFilterChain
{
private final FilterHandler[] handlers;
private final ServletPipeline servletPipeline;
- private final FilterChain proceedingChain;
+ private final FilterChain proceedingChain;
private int index = -1;
public InvocationFilterChain(FilterHandler[] handlers, ServletPipeline servletPipeline, FilterChain proceedingChain)
@@ -38,15 +37,18 @@
this.proceedingChain = proceedingChain;
}
- protected void doFilter(HttpServletRequest req, HttpServletResponse res)
- throws IOException, ServletException
+ protected void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException
{
this.index++;
- if (this.index < this.handlers.length) {
+ if (this.index < this.handlers.length)
+ {
this.handlers[this.index].handle(req, res, this);
- } else {
- if (!this.servletPipeline.handle(req, res)) {
+ }
+ else
+ {
+ if (!this.servletPipeline.handle(req, res))
+ {
this.proceedingChain.doFilter(req, res);
}
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
index 3ee23b2..4f0d012 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
@@ -21,11 +21,9 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-public final class NotFoundFilterChain
- extends HttpFilterChain
+public final class NotFoundFilterChain extends HttpFilterChain
{
- protected void doFilter(HttpServletRequest req, HttpServletResponse res)
- throws IOException, ServletException
+ protected void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException
{
res.sendError(HttpServletResponse.SC_NOT_FOUND);
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java
new file mode 100644
index 0000000..a863bd2
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.base.internal.dispatch;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface RequestDispatcherProvider
+{
+ /**
+ * @see ServletContext#getNamedDispatcher(String)
+ */
+ RequestDispatcher getNamedDispatcher(String name);
+
+ /**
+ * @see ServletContext#getRequestDispatcher(String)
+ */
+ RequestDispatcher getRequestDispatcher(String path);
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java
index 053a09d..b2618f2 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java
@@ -16,17 +16,19 @@
*/
package org.apache.felix.http.base.internal.dispatch;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.ServletException;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import static org.apache.felix.http.base.internal.util.UriUtils.decodePath;
+import static org.apache.felix.http.base.internal.util.UriUtils.removeDotSegments;
+
import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.apache.felix.http.base.internal.handler.ServletHandler;
-public final class ServletPipeline
+public final class ServletPipeline implements RequestDispatcherProvider
{
private final ServletHandler[] handlers;
@@ -35,11 +37,61 @@
this.handlers = handlers;
}
- public boolean handle(HttpServletRequest req, HttpServletResponse res)
- throws ServletException, IOException
+ public RequestDispatcher getNamedDispatcher(String name)
{
- for (ServletHandler handler : this.handlers) {
- if (handler.handle(req, res)) {
+ // See section 9.1 of Servlet 3.x specification...
+ if (name == null)
+ {
+ return null;
+ }
+
+ for (ServletHandler handler : this.handlers)
+ {
+ if (name.equals(handler.getName()))
+ {
+ return handler.createNamedRequestDispatcher();
+ }
+ }
+
+ return null;
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ // See section 9.1 of Servlet 3.x specification...
+ if (path == null || (!path.startsWith("/") && !"".equals(path)))
+ {
+ return null;
+ }
+
+ String query = null;
+ int q = 0;
+ if ((q = path.indexOf('?')) > 0)
+ {
+ query = path.substring(q + 1);
+ path = path.substring(0, q);
+ }
+ // TODO remove path parameters...
+ String pathInContext = decodePath(removeDotSegments(path));
+
+ for (ServletHandler handler : this.handlers)
+ {
+ if (handler.matches(pathInContext))
+ {
+ return handler.createRequestDispatcher(path, pathInContext, query);
+ }
+ }
+
+ return null;
+ }
+
+ public boolean handle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
+ {
+ // NOTE: this code assumes that HttpServletRequest#getRequestDispatcher() is properly mapped, see FilterPipeline.FilterRequestWrapper!
+ for (ServletHandler handler : this.handlers)
+ {
+ if (handler.handle(req, res))
+ {
return true;
}
}
@@ -51,61 +103,4 @@
{
return this.handlers.length > 0;
}
-
- public RequestDispatcher getRequestDispatcher(String path)
- {
- for (ServletHandler handler : this.handlers) {
- if (handler.matches(path)) {
- return new Dispatcher(path, handler);
- }
- }
-
- return null;
- }
-
- private final class Dispatcher
- implements RequestDispatcher
- {
- private final String path;
- private final ServletHandler handler;
-
- public Dispatcher(String path, ServletHandler handler)
- {
- this.path = path;
- this.handler = handler;
- }
-
- public void forward(ServletRequest req, ServletResponse res)
- throws ServletException, IOException
- {
- if (res.isCommitted()) {
- throw new ServletException("Response has been committed");
- }
-
- this.handler.handle(new RequestWrapper((HttpServletRequest)req, this.path), (HttpServletResponse)res);
- }
-
- public void include(ServletRequest req, ServletResponse res)
- throws ServletException, IOException
- {
- this.handler.handle((HttpServletRequest)req, (HttpServletResponse)res);
- }
- }
-
- private final class RequestWrapper
- extends HttpServletRequestWrapper
- {
- private final String requestUri;
-
- public RequestWrapper(HttpServletRequest req, String requestUri)
- {
- super(req);
- this.requestUri = requestUri;
- }
-
- public String getRequestURI()
- {
- return this.requestUri;
- }
- }
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java
index f5c7041..008732e 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java
@@ -16,30 +16,70 @@
*/
package org.apache.felix.http.base.internal.handler;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
import javax.servlet.ServletException;
+
import org.apache.felix.http.base.internal.context.ExtServletContext;
+
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractHandler
{
- private final static AtomicInteger ID =
- new AtomicInteger();
+ private final static AtomicInteger ID = new AtomicInteger();
- private final String id;
+ private final int id;
+ private final String baseName;
private final ExtServletContext context;
private final Map<String, String> initParams;
- public AbstractHandler(ExtServletContext context)
+ public AbstractHandler(ExtServletContext context, String baseName)
{
- this.id = "" + ID.incrementAndGet();
this.context = context;
+ this.baseName = baseName;
+ this.id = ID.incrementAndGet();
this.initParams = new HashMap<String, String>();
}
- public final String getId()
+ public abstract void destroy();
+
+ public final Map<String, String> getInitParams()
{
- return this.id;
+ return this.initParams;
+ }
+
+ public final String getName()
+ {
+ String name = this.baseName;
+ if (name == null)
+ {
+ name = String.format("%s_%d", getSubject().getClass(), this.id);
+ }
+ return name;
+ }
+
+ public abstract void init() throws ServletException;
+
+ public final void setInitParams(Dictionary map)
+ {
+ this.initParams.clear();
+ if (map == null)
+ {
+ return;
+ }
+
+ Enumeration e = map.keys();
+ while (e.hasMoreElements())
+ {
+ Object key = e.nextElement();
+ Object value = map.get(key);
+
+ if ((key instanceof String) && (value instanceof String))
+ {
+ this.initParams.put((String) key, (String) value);
+ }
+ }
}
protected final ExtServletContext getContext()
@@ -47,31 +87,16 @@
return this.context;
}
- public final Map<String, String> getInitParams()
+ /**
+ * @return a unique ID for this handler, > 0.
+ */
+ protected final int getId()
{
- return this.initParams;
+ return id;
}
- public final void setInitParams(Dictionary map)
- {
- this.initParams.clear();
- if (map == null) {
- return;
- }
-
- Enumeration e = map.keys();
- while (e.hasMoreElements()) {
- Object key = e.nextElement();
- Object value = map.get(key);
-
- if ((key instanceof String) && (value instanceof String)) {
- this.initParams.put((String)key, (String)value);
- }
- }
- }
-
- public abstract void init()
- throws ServletException;
-
- public abstract void destroy();
+ /**
+ * @return the {@link Servlet} or {@link Filter} this handler handles.
+ */
+ protected abstract Object getSubject();
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java
index b807fe1..f0bcdab 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java
@@ -22,8 +22,7 @@
import java.util.Collections;
import java.util.Map;
-public final class FilterConfigImpl
- implements FilterConfig
+public final class FilterConfigImpl implements FilterConfig
{
private final String name;
private final ServletContext context;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
index f28bc31..52770fe 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
@@ -19,74 +19,26 @@
import java.io.IOException;
import java.util.regex.Pattern;
-import javax.servlet.*;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.felix.http.base.internal.context.ExtServletContext;
-public final class FilterHandler
- extends AbstractHandler implements Comparable<FilterHandler>
+public final class FilterHandler extends AbstractHandler implements Comparable<FilterHandler>
{
private final Filter filter;
private final Pattern regex;
private final int ranking;
- public FilterHandler(ExtServletContext context, Filter filter, String pattern, int ranking)
+ public FilterHandler(ExtServletContext context, Filter filter, String pattern, int ranking, String name)
{
- super(context);
+ super(context, name);
this.filter = filter;
this.ranking = ranking;
- this.regex = Pattern.compile(pattern);
- }
-
- public Filter getFilter()
- {
- return this.filter;
- }
-
- public void init()
- throws ServletException
- {
- String name = "filter_" + getId();
- FilterConfig config = new FilterConfigImpl(name, getContext(), getInitParams());
- this.filter.init(config);
- }
-
- public void destroy()
- {
- this.filter.destroy();
- }
-
- public boolean matches(String uri)
- {
- // assume root if uri is null
- if (uri == null) {
- uri = "/";
- }
-
- return this.regex.matcher(uri).matches();
- }
-
- public void handle(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
- throws ServletException, IOException
- {
- final boolean matches = matches(req.getPathInfo());
- if (matches) {
- doHandle(req, res, chain);
- } else {
- chain.doFilter(req, res);
- }
- }
-
- private void doHandle(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
- throws ServletException, IOException
- {
- if (!getContext().handleSecurity(req, res)) {
- res.sendError(HttpServletResponse.SC_FORBIDDEN);
- } else {
- this.filter.doFilter(req, res, chain);
- }
+ this.regex = Pattern.compile(pattern);
}
public int compareTo(FilterHandler other)
@@ -99,13 +51,70 @@
return (other.ranking > this.ranking) ? 1 : -1;
}
- public int getRanking()
+ public void destroy()
{
- return ranking;
+ this.filter.destroy();
+ }
+
+ public Filter getFilter()
+ {
+ return this.filter;
}
public String getPattern()
{
return regex.toString();
}
+
+ public int getRanking()
+ {
+ return ranking;
+ }
+
+ public void handle(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException
+ {
+ final boolean matches = matches(req.getPathInfo());
+ if (matches)
+ {
+ doHandle(req, res, chain);
+ }
+ else
+ {
+ chain.doFilter(req, res);
+ }
+ }
+
+ public void init() throws ServletException
+ {
+ this.filter.init(new FilterConfigImpl(getName(), getContext(), getInitParams()));
+ }
+
+ public boolean matches(String uri)
+ {
+ // assume root if uri is null
+ if (uri == null)
+ {
+ uri = "/";
+ }
+
+ return this.regex.matcher(uri).matches();
+ }
+
+ final void doHandle(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException
+ {
+ if (!getContext().handleSecurity(req, res))
+ {
+ res.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ else
+ {
+ this.filter.doFilter(req, res, chain);
+ }
+ }
+
+ @Override
+ protected Object getSubject()
+ {
+ return this.filter;
+ }
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
index 6e6c503..0b9ee16 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
@@ -51,14 +51,15 @@
return this.filters;
}
- public synchronized void addServlet(ServletHandler handler)
- throws ServletException, NamespaceException
+ public synchronized void addServlet(ServletHandler handler) throws ServletException, NamespaceException
{
- if (this.servletMap.containsKey(handler.getServlet())) {
+ if (this.servletMap.containsKey(handler.getServlet()))
+ {
throw new ServletException("Servlet instance already registered");
}
- if (this.aliasMap.containsKey(handler.getAlias())) {
+ if (this.aliasMap.containsKey(handler.getAlias()))
+ {
throw new NamespaceException("Servlet with alias already registered");
}
@@ -68,10 +69,10 @@
updateServletArray();
}
- public synchronized void addFilter(FilterHandler handler)
- throws ServletException
+ public synchronized void addFilter(FilterHandler handler) throws ServletException
{
- if (this.filterMap.containsKey(handler.getFilter())) {
+ if (this.filterMap.containsKey(handler.getFilter()))
+ {
throw new ServletException("Filter instance already registered");
}
@@ -83,7 +84,8 @@
public synchronized void removeServlet(Servlet servlet, final boolean destroy)
{
ServletHandler handler = this.servletMap.remove(servlet);
- if (handler != null) {
+ if (handler != null)
+ {
updateServletArray();
this.aliasMap.remove(handler.getAlias());
if (destroy)
@@ -96,7 +98,8 @@
public synchronized void removeFilter(Filter filter, final boolean destroy)
{
FilterHandler handler = this.filterMap.remove(filter);
- if (handler != null) {
+ if (handler != null)
+ {
updateFilterArray();
if (destroy)
{
@@ -112,11 +115,13 @@
public synchronized void removeAll()
{
- for (ServletHandler handler : this.servletMap.values()) {
+ for (ServletHandler handler : this.servletMap.values())
+ {
handler.destroy();
}
- for (FilterHandler handler : this.filterMap.values()) {
+ for (FilterHandler handler : this.filterMap.values())
+ {
handler.destroy();
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpSessionWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpSessionWrapper.java
new file mode 100644
index 0000000..602a106
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpSessionWrapper.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.base.internal.handler;
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("deprecation")
+public class HttpSessionWrapper implements HttpSession
+{
+ private final HttpSession delegate;
+ private final ServletContext context;
+
+ /**
+ * Creates a new {@link HttpSessionWrapper} instance.
+ */
+ public HttpSessionWrapper(HttpSession session, ServletContext context)
+ {
+ this.delegate = session;
+ this.context = context;
+ }
+
+ public Object getAttribute(String name)
+ {
+ return this.delegate.getAttribute(name);
+ }
+
+ public Enumeration<String> getAttributeNames()
+ {
+ return this.delegate.getAttributeNames();
+ }
+
+ public long getCreationTime()
+ {
+ return this.delegate.getCreationTime();
+ }
+
+ public String getId()
+ {
+ return this.delegate.getId();
+ }
+
+ public long getLastAccessedTime()
+ {
+ return this.delegate.getLastAccessedTime();
+ }
+
+ public int getMaxInactiveInterval()
+ {
+ return this.delegate.getMaxInactiveInterval();
+ }
+
+ public ServletContext getServletContext()
+ {
+ return this.context;
+ }
+
+ public HttpSessionContext getSessionContext()
+ {
+ return this.delegate.getSessionContext();
+ }
+
+ public Object getValue(String name)
+ {
+ return this.delegate.getValue(name);
+ }
+
+ public String[] getValueNames()
+ {
+ return this.delegate.getValueNames();
+ }
+
+ public void invalidate()
+ {
+ this.delegate.invalidate();
+ }
+
+ public boolean isNew()
+ {
+ return this.delegate.isNew();
+ }
+
+ public void putValue(String name, Object value)
+ {
+ this.delegate.putValue(name, value);
+ }
+
+ public void removeAttribute(String name)
+ {
+ this.delegate.removeAttribute(name);
+ }
+
+ public void removeValue(String name)
+ {
+ this.delegate.removeValue(name);
+ }
+
+ public void setAttribute(String name, Object value)
+ {
+ this.delegate.setAttribute(name, value);
+ }
+
+ public void setMaxInactiveInterval(int interval)
+ {
+ this.delegate.setMaxInactiveInterval(interval);
+ }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java
index 43dc2b1..c3a0c7f 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java
@@ -22,8 +22,7 @@
import java.util.Collections;
import java.util.Map;
-public final class ServletConfigImpl
- implements ServletConfig
+public final class ServletConfigImpl implements ServletConfig
{
private final String name;
private final ServletContext context;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletContextWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletContextWrapper.java
new file mode 100644
index 0000000..33822fc
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletContextWrapper.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.base.internal.handler;
+
+import javax.servlet.RequestDispatcher;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.context.ServletContextImpl;
+import org.apache.felix.http.base.internal.dispatch.RequestDispatcherProvider;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+class ServletContextWrapper extends ServletContextImpl
+{
+ private final RequestDispatcherProvider provider;
+
+ /**
+ * Creates a new {@link ServletContextWrapper} instance.
+ */
+ public ServletContextWrapper(ExtServletContext delegate, RequestDispatcherProvider provider)
+ {
+ super(delegate);
+
+ this.provider = provider;
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name)
+ {
+ if (name == null)
+ {
+ return null;
+ }
+
+ RequestDispatcher dispatcher = this.provider.getNamedDispatcher(name);
+ return dispatcher != null ? dispatcher : super.getNamedDispatcher(name);
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ // See section 9.1 of Servlet 3.x specification...
+ if (path == null || (!path.startsWith("/") && !"".equals(path)))
+ {
+ return null;
+ }
+
+ RequestDispatcher dispatcher = this.provider.getRequestDispatcher(path);
+ return dispatcher != null ? dispatcher : super.getRequestDispatcher(path);
+ }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
index a47db6c..cf392b9 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
@@ -16,27 +16,252 @@
*/
package org.apache.felix.http.base.internal.handler;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-import javax.servlet.ServletConfig;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.apache.felix.http.base.internal.context.ExtServletContext;
+import static javax.servlet.RequestDispatcher.FORWARD_CONTEXT_PATH;
+import static javax.servlet.RequestDispatcher.FORWARD_PATH_INFO;
+import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.FORWARD_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.FORWARD_SERVLET_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_CONTEXT_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_PATH_INFO;
+import static javax.servlet.RequestDispatcher.INCLUDE_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;
+import static org.apache.felix.http.base.internal.util.UriUtils.concat;
+
import java.io.IOException;
-public final class ServletHandler
- extends AbstractHandler implements Comparable<ServletHandler>
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class ServletHandler extends AbstractHandler implements Comparable<ServletHandler>
{
+ private class RequestDispatcherImpl implements RequestDispatcher
+ {
+ final String servletPath;
+ final String requestURI;
+ final String pathInfo;
+ final String query;
+ final boolean named;
+
+ public RequestDispatcherImpl()
+ {
+ this.requestURI = null;
+ // PathMap.pathMatch(servlet_path_spec,target);
+ this.servletPath = getAlias(); // XXX handle wildcard aliases!
+ this.pathInfo = null;
+ this.query = null;
+ this.named = true;
+ }
+
+ public RequestDispatcherImpl(String uri, String pathInContext, String query)
+ {
+ this.requestURI = uri;
+ this.servletPath = getAlias(); // XXX handle wildcard aliases!
+ this.pathInfo = this.servletPath.equals(pathInContext) ? null : pathInContext;
+ this.query = query;
+ this.named = false;
+ }
+
+ public void forward(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ if (res.isCommitted())
+ {
+ throw new ServletException("Response has been committed");
+ }
+ else
+ {
+ // See section 9.4 of Servlet 3.0 spec
+ res.resetBuffer();
+ }
+
+ // Since we're already created this RequestDispatcher for *this* servlet handler, we do not need to
+ // recheck whether its patch matches, but instead can directly handle the forward-request...
+ doHandle(new ServletRequestWrapper((HttpServletRequest) req, this, DispatcherType.FORWARD), (HttpServletResponse) res);
+
+ // After a forward has taken place, the results should be committed,
+ // see section 9.4 of Servlet 3.0 spec...
+ if (!req.isAsyncStarted())
+ {
+ res.flushBuffer();
+ res.getWriter().close();
+ }
+ }
+
+ public void include(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ // Since we're already created this RequestDispatcher for *this* servlet handler, we do not need to
+ // recheck whether its patch matches, but instead can directly handle the include-request...
+ doHandle(new ServletRequestWrapper((HttpServletRequest) req, this, DispatcherType.INCLUDE), (HttpServletResponse) res);
+ }
+
+ boolean isNamedDispatcher()
+ {
+ return this.named;
+ }
+ }
+
+ private static class ServletRequestWrapper extends HttpServletRequestWrapper
+ {
+ private final RequestDispatcherImpl dispatcher;
+ private final DispatcherType type;
+
+ public ServletRequestWrapper(HttpServletRequest req, RequestDispatcherImpl dispatcher, DispatcherType type)
+ {
+ super(req);
+ this.dispatcher = dispatcher;
+ this.type = type;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ HttpServletRequest request = (HttpServletRequest) getRequest();
+ if (isInclusionDispatcher())
+ {
+ if (INCLUDE_REQUEST_URI.equals(name))
+ {
+ return concat(request.getContextPath(), this.dispatcher.requestURI);
+ }
+ else if (INCLUDE_CONTEXT_PATH.equals(name))
+ {
+ return request.getContextPath();
+ }
+ else if (INCLUDE_SERVLET_PATH.equals(name))
+ {
+ return this.dispatcher.servletPath;
+ }
+ else if (INCLUDE_PATH_INFO.equals(name))
+ {
+ return this.dispatcher.pathInfo;
+ }
+ else if (INCLUDE_QUERY_STRING.equals(name))
+ {
+ return this.dispatcher.query;
+ }
+ }
+ else if (isForwardingDispatcher())
+ {
+ // NOTE: the forward.* attributes *always* yield the *original* values...
+ if (FORWARD_REQUEST_URI.equals(name))
+ {
+ return super.getRequestURI();
+ }
+ else if (FORWARD_CONTEXT_PATH.equals(name))
+ {
+ return request.getContextPath();
+ }
+ else if (FORWARD_SERVLET_PATH.equals(name))
+ {
+ return super.getServletPath();
+ }
+ else if (FORWARD_PATH_INFO.equals(name))
+ {
+ return super.getPathInfo();
+ }
+ else if (FORWARD_QUERY_STRING.equals(name))
+ {
+ return super.getQueryString();
+ }
+ }
+
+ return super.getAttribute(name);
+ }
+
+ @Override
+ public DispatcherType getDispatcherType()
+ {
+ return this.type;
+ }
+
+ @Override
+ public String getPathInfo()
+ {
+ if (isForwardingDispatcher())
+ {
+ return this.dispatcher.pathInfo;
+ }
+ return super.getPathInfo();
+ }
+
+ @Override
+ public String getRequestURI()
+ {
+ if (isForwardingDispatcher())
+ {
+ return concat(getContextPath(), this.dispatcher.requestURI);
+ }
+ return super.getRequestURI();
+ }
+
+ @Override
+ public String getServletPath()
+ {
+ if (isForwardingDispatcher())
+ {
+ return this.dispatcher.servletPath;
+ }
+ return super.getServletPath();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "->" + super.getRequest();
+ }
+
+ private boolean isForwardingDispatcher()
+ {
+ return DispatcherType.FORWARD == this.type && !this.dispatcher.isNamedDispatcher();
+ }
+
+ private boolean isInclusionDispatcher()
+ {
+ return DispatcherType.INCLUDE == this.type && !this.dispatcher.isNamedDispatcher();
+ }
+ }
+
private final String alias;
private final Servlet servlet;
- public ServletHandler(ExtServletContext context, Servlet servlet, String alias)
+ public ServletHandler(ExtServletContext context, Servlet servlet, String alias, String name)
{
- super(context);
+ super(context, name);
this.alias = alias;
this.servlet = servlet;
}
+ public int compareTo(ServletHandler other)
+ {
+ return other.alias.length() - this.alias.length();
+ }
+
+ public RequestDispatcher createNamedRequestDispatcher()
+ {
+ return new RequestDispatcherImpl();
+ }
+
+ public RequestDispatcher createRequestDispatcher(String path, String pathInContext, String query)
+ {
+ return new RequestDispatcherImpl(path, pathInContext, query);
+ }
+
+ public void destroy()
+ {
+ this.servlet.destroy();
+ }
+
public String getAlias()
{
return this.alias;
@@ -47,43 +272,58 @@
return this.servlet;
}
- public void init()
- throws ServletException
+ public boolean handle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
- String name = "servlet_" + getId();
- ServletConfig config = new ServletConfigImpl(name, getContext(), getInitParams());
- this.servlet.init(config);
- }
-
- public void destroy()
- {
- this.servlet.destroy();
- }
-
- public boolean matches(String uri)
- {
- if (uri == null) {
- return this.alias.equals("/");
- } else if (this.alias.equals("/")) {
- return uri.startsWith(this.alias);
- } else {
- return uri.equals(this.alias) || uri.startsWith(this.alias + "/");
+ String path;
+ if (DispatcherType.INCLUDE == req.getDispatcherType())
+ {
+ path = (String) req.getAttribute(INCLUDE_SERVLET_PATH);
}
- }
+ else if (DispatcherType.FORWARD == req.getDispatcherType())
+ {
+ path = (String) req.getAttribute(FORWARD_SERVLET_PATH);
+ }
+ else if (DispatcherType.ASYNC == req.getDispatcherType())
+ {
+ path = (String) req.getAttribute("javax.servlet.async.path_info");
+ }
+ else
+ {
+ path = req.getPathInfo();
+ }
- public boolean handle(HttpServletRequest req, HttpServletResponse res)
- throws ServletException, IOException
- {
- final boolean matches = matches(req.getPathInfo());
- if (matches) {
+ final boolean matches = matches(path);
+ if (matches)
+ {
doHandle(req, res);
}
return matches;
}
- private void doHandle(HttpServletRequest req, HttpServletResponse res)
- throws ServletException, IOException
+ public void init() throws ServletException
+ {
+ this.servlet.init(new ServletConfigImpl(getName(), getContext(), getInitParams()));
+ }
+
+ public boolean matches(String uri)
+ {
+ // TODO handle wildcard aliases and extension specs...
+ if (uri == null)
+ {
+ return this.alias.equals("/");
+ }
+ else if (this.alias.equals("/"))
+ {
+ return uri.startsWith(this.alias);
+ }
+ else
+ {
+ return uri.equals(this.alias) || uri.startsWith(this.alias + "/");
+ }
+ }
+
+ final void doHandle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
// set a sensible status code in case handleSecurity returns false
// but fails to send a response
@@ -93,12 +333,22 @@
// reset status to OK for further processing
res.setStatus(HttpServletResponse.SC_OK);
- this.servlet.service(new ServletHandlerRequest(req, this.alias), res);
+ // Only wrap the original ServletRequest in case we're handling plain requests,
+ // not inclusions or forwards from servlets. Should solve FELIX-2774 (partly)...
+ if (DispatcherType.REQUEST == req.getDispatcherType())
+ {
+ this.servlet.service(new ServletHandlerRequest(req, getContext(), this.alias), res);
+ }
+ else
+ {
+ this.servlet.service(req, res);
+ }
}
}
- public int compareTo(ServletHandler other)
+ @Override
+ protected Object getSubject()
{
- return other.alias.length() - this.alias.length();
+ return this.servlet;
}
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java
index 1e3c10e..9e6a9cf 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java
@@ -16,22 +16,35 @@
*/
package org.apache.felix.http.base.internal.handler;
-import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.dispatch.Dispatcher;
+import org.apache.felix.http.base.internal.dispatch.RequestDispatcherProvider;
+import org.apache.felix.http.base.internal.util.UriUtils;
import org.osgi.service.http.HttpContext;
+import org.osgi.service.useradmin.Authorization;
-final class ServletHandlerRequest
- extends HttpServletRequestWrapper
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("deprecation")
+class ServletHandlerRequest extends HttpServletRequestWrapper
{
private final String alias;
+ private final ServletContextWrapper context;
private String contextPath;
private String pathInfo;
private boolean pathInfoCalculated = false;
- public ServletHandlerRequest(HttpServletRequest req, String alias)
+ public ServletHandlerRequest(HttpServletRequest req, ExtServletContext context, String alias)
{
super(req);
+ this.context = new ServletContextWrapper(context, (RequestDispatcherProvider) req.getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER));
this.alias = alias;
}
@@ -39,7 +52,8 @@
public String getAuthType()
{
String authType = (String) getAttribute(HttpContext.AUTHENTICATION_TYPE);
- if (authType != null) {
+ if (authType != null)
+ {
return authType;
}
@@ -53,14 +67,20 @@
* FELIX-2030 Calculate the context path for the Http Service
* registered servlets from the container context and servlet paths
*/
- if (contextPath == null) {
+ if (contextPath == null)
+ {
final String context = super.getContextPath();
final String servlet = super.getServletPath();
- if (context.length() == 0) {
+ if (context == null || context.length() == 0)
+ {
contextPath = servlet;
- } else if (servlet.length() == 0) {
+ }
+ else if (servlet == null || servlet.length() == 0)
+ {
contextPath = context;
- } else {
+ }
+ else
+ {
contextPath = context + servlet;
}
}
@@ -71,7 +91,8 @@
@Override
public String getPathInfo()
{
- if (!this.pathInfoCalculated) {
+ if (!this.pathInfoCalculated)
+ {
this.pathInfo = calculatePathInfo();
this.pathInfoCalculated = true;
}
@@ -90,7 +111,8 @@
public String getRemoteUser()
{
String remoteUser = (String) getAttribute(HttpContext.REMOTE_USER);
- if (remoteUser != null) {
+ if (remoteUser != null)
+ {
return remoteUser;
}
@@ -98,14 +120,68 @@
}
@Override
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ // See section 9.1 of Servlet 3.0 specification...
+ if (path == null)
+ {
+ return null;
+ }
+ // Handle relative paths, see Servlet 3.0 spec, section 9.1 last paragraph.
+ boolean relPath = !path.startsWith("/") && !"".equals(path);
+ if (relPath)
+ {
+ path = UriUtils.concat(this.alias, path);
+ }
+ return super.getRequestDispatcher(path);
+ }
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ return this.context;
+ }
+
+ @Override
public String getServletPath()
{
- if ("/".equals(this.alias)) {
+ if ("/".equals(this.alias))
+ {
return "";
}
return this.alias;
}
+ @Override
+ public HttpSession getSession(boolean create)
+ {
+ // FELIX-2797: wrap the original HttpSession to provide access to the correct ServletContext...
+ HttpSession session = super.getSession(create);
+ if (session == null)
+ {
+ return null;
+ }
+ return new HttpSessionWrapper(session, this.context);
+ }
+
+ @Override
+ public boolean isUserInRole(String role)
+ {
+ Authorization authorization = (Authorization) getAttribute(HttpContext.AUTHORIZATION);
+ if (authorization != null)
+ {
+ return authorization.hasRole(role);
+ }
+
+ return super.isUserInRole(role);
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "->" + super.getRequest();
+ }
+
private String calculatePathInfo()
{
/*
@@ -118,20 +194,20 @@
* Note, the servlet container pathInfo may also be null if the
* servlet is registered as the root servlet
*/
-
String pathInfo = super.getPathInfo();
- if (pathInfo != null) {
-
+ if (pathInfo != null)
+ {
// cut off alias of this servlet (if not the root servlet)
- if (!"/".equals(alias)) {
+ if (!"/".equals(this.alias) && pathInfo.startsWith(this.alias))
+ {
pathInfo = pathInfo.substring(alias.length());
}
// ensure empty string is coerced to null
- if (pathInfo.length() == 0) {
+ if (pathInfo.length() == 0)
+ {
pathInfo = null;
}
-
}
return pathInfo;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java
index b0e586b..9d59382 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java
@@ -30,8 +30,7 @@
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.NamespaceException;
-public final class HttpServiceImpl
- implements ExtHttpService
+public final class HttpServiceImpl implements ExtHttpService
{
private final Bundle bundle;
private final HandlerRegistry handlerRegistry;
@@ -39,33 +38,33 @@
private final HashSet<Filter> localFilters;
private final ServletContextManager contextManager;
- public HttpServiceImpl(Bundle bundle, ServletContext context, HandlerRegistry handlerRegistry,
- ServletContextAttributeListener servletAttributeListener, boolean sharedContextAttributes)
+ public HttpServiceImpl(Bundle bundle, ServletContext context, HandlerRegistry handlerRegistry, ServletContextAttributeListener servletAttributeListener, boolean sharedContextAttributes)
{
this.bundle = bundle;
this.handlerRegistry = handlerRegistry;
this.localServlets = new HashSet<Servlet>();
this.localFilters = new HashSet<Filter>();
- this.contextManager = new ServletContextManager(this.bundle, context, servletAttributeListener,
- sharedContextAttributes);
+ this.contextManager = new ServletContextManager(this.bundle, context, servletAttributeListener, sharedContextAttributes);
}
private ExtServletContext getServletContext(HttpContext context)
{
- if (context == null) {
+ if (context == null)
+ {
context = createDefaultHttpContext();
}
return this.contextManager.getServletContext(context);
}
- public void registerFilter(Filter filter, String pattern, Dictionary initParams, int ranking, HttpContext context)
- throws ServletException
+ public void registerFilter(Filter filter, String pattern, Dictionary initParams, int ranking, HttpContext context) throws ServletException
{
- if (filter == null ) {
+ if (filter == null)
+ {
throw new IllegalArgumentException("Filter must not be null");
}
- FilterHandler handler = new FilterHandler(getServletContext(context), filter, pattern, ranking);
+ String filterName = null; // XXX
+ FilterHandler handler = new FilterHandler(getServletContext(context), filter, pattern, ranking, filterName);
handler.setInitParams(initParams);
this.handlerRegistry.addFilter(handler);
this.localFilters.add(filter);
@@ -81,32 +80,37 @@
unregisterServlet(servlet, true);
}
- public void registerServlet(String alias, Servlet servlet, Dictionary initParams, HttpContext context)
- throws ServletException, NamespaceException
+ public void registerServlet(String alias, Servlet servlet, Dictionary initParams, HttpContext context) throws ServletException, NamespaceException
{
- if (servlet == null ) {
+ if (servlet == null)
+ {
throw new IllegalArgumentException("Servlet must not be null");
}
- if (!isAliasValid(alias)) {
- throw new IllegalArgumentException( "Malformed servlet alias [" + alias + "]");
+ if (!isAliasValid(alias))
+ {
+ throw new IllegalArgumentException("Malformed servlet alias [" + alias + "]");
}
- ServletHandler handler = new ServletHandler(getServletContext(context), servlet, alias);
+ String servletName = null; // XXX
+ ServletHandler handler = new ServletHandler(getServletContext(context), servlet, alias, servletName);
handler.setInitParams(initParams);
this.handlerRegistry.addServlet(handler);
this.localServlets.add(servlet);
}
- public void registerResources(String alias, String name, HttpContext context)
- throws NamespaceException
+ public void registerResources(String alias, String name, HttpContext context) throws NamespaceException
{
- if (!isNameValid(name)) {
- throw new IllegalArgumentException( "Malformed resource name [" + name + "]");
+ if (!isNameValid(name))
+ {
+ throw new IllegalArgumentException("Malformed resource name [" + name + "]");
}
- try {
+ try
+ {
Servlet servlet = new ResourceServlet(name);
registerServlet(alias, servlet, null, context);
- } catch (ServletException e) {
+ }
+ catch (ServletException e)
+ {
SystemLogger.error("Failed to register resources", e);
}
}
@@ -124,19 +128,22 @@
public void unregisterAll()
{
HashSet<Servlet> servlets = new HashSet<Servlet>(this.localServlets);
- for (Servlet servlet : servlets) {
+ for (Servlet servlet : servlets)
+ {
unregisterServlet(servlet, false);
}
HashSet<Filter> filters = new HashSet<Filter>(this.localFilters);
- for (Filter fiter : filters) {
+ for (Filter fiter : filters)
+ {
unregisterFilter(fiter, false);
}
}
private void unregisterFilter(Filter filter, final boolean destroy)
{
- if (filter != null) {
+ if (filter != null)
+ {
this.handlerRegistry.removeFilter(filter, destroy);
this.localFilters.remove(filter);
}
@@ -144,7 +151,8 @@
private void unregisterServlet(Servlet servlet, final boolean destroy)
{
- if (servlet != null) {
+ if (servlet != null)
+ {
this.handlerRegistry.removeServlet(servlet, destroy);
this.localServlets.remove(servlet);
}
@@ -152,11 +160,13 @@
private boolean isNameValid(String name)
{
- if (name == null) {
+ if (name == null)
+ {
return false;
}
- if (!name.equals("/") && name.endsWith( "/" )) {
+ if (!name.equals("/") && name.endsWith("/"))
+ {
return false;
}
@@ -165,11 +175,13 @@
private boolean isAliasValid(String alias)
{
- if (alias == null) {
+ if (alias == null)
+ {
return false;
}
- if (!alias.equals("/") && ( !alias.startsWith("/") || alias.endsWith("/"))) {
+ if (!alias.equals("/") && (!alias.startsWith("/") || alias.endsWith("/")))
+ {
return false;
}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/util/UriUtils.java b/http/base/src/main/java/org/apache/felix/http/base/internal/util/UriUtils.java
new file mode 100644
index 0000000..4646089
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/util/UriUtils.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.base.internal.util;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Some convenience methods for handling URI(-parts).
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class UriUtils
+{
+ private static final String SLASH = "/";
+
+ /**
+ * Concatenates two paths keeping their respective path-parts into consideration.
+ *
+ * @param path1 the first part of the path, can be <code>null</code>;
+ * @param path2 the second part of the path, can be <code>null</code>.
+ * @return the concatenated path, can be <code>null</code> in case both given arguments were <code>null</code>.
+ */
+ public static String concat(String path1, String path2)
+ {
+ // Handle special cases...
+ if (path1 == null && path2 == null)
+ {
+ return null;
+ }
+ if (path1 == null)
+ {
+ path1 = "";
+ }
+ if (path2 == null)
+ {
+ path2 = "";
+ }
+ if (isEmpty(path1) && isEmpty(path2))
+ {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ int idx = path1.indexOf('?');
+ if (idx == 0)
+ {
+ // path1 only consists of a query, append it to the second path...
+ return path2.concat(path1);
+ }
+ else if (idx > 0)
+ {
+ // path1 contains of a path + query, append the path first...
+ sb.append(path1.substring(0, idx));
+ }
+ else
+ {
+ // Plain paths...
+ sb.append(path1);
+ // need a slash?
+ }
+
+ if (endsWith(sb, SLASH))
+ {
+ if (path2.startsWith(SLASH))
+ {
+ sb.append(path2.substring(1));
+ }
+ else
+ {
+ sb.append(path2);
+ }
+ }
+ else
+ {
+ if (path2.startsWith(SLASH))
+ {
+ sb.append(path2);
+ }
+ else if (sb.length() > 0 && !isEmpty(path2))
+ {
+ sb.append(SLASH).append(path2);
+ }
+ else
+ {
+ sb.append(path2);
+ }
+ }
+
+ if (idx > 0)
+ {
+ // Add the query of path1...
+ sb.append(path1.substring(idx, path1.length()));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Decodes a given URL-encoded path assuming it is UTF-8 encoded.
+ *
+ * @param path the URL-encoded path, can be <code>null</code>.
+ * @return the decoded path, can be <code>null</code> only if the given path was <code>null</code>.
+ */
+ public static String decodePath(String path)
+ {
+ return decodePath(path, "UTF-8");
+ }
+
+ /**
+ * Decodes a given URL-encoded path using a given character encoding.
+ *
+ * @param path the URL-encoded path, can be <code>null</code>;
+ * @param encoding the character encoding to use, cannot be <code>null</code>.
+ * @return the decoded path, can be <code>null</code> only if the given path was <code>null</code>.
+ */
+ public static String decodePath(String path, String encoding)
+ {
+ // Special cases...
+ if (path == null)
+ {
+ return null;
+ }
+
+ CharsetDecoder decoder = Charset.forName(encoding).newDecoder();
+ decoder.onMalformedInput(CodingErrorAction.REPORT);
+ decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+
+ int len = path.length();
+ ByteBuffer buf = ByteBuffer.allocate(len);
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < len; i++)
+ {
+ char ch = path.charAt(i);
+ if (ch == '%' && (i + 2 < len))
+ {
+ // URL-encoded char...
+ buf.put((byte) ((16 * hexVal(path, ++i)) + hexVal(path, ++i)));
+ }
+ else
+ {
+ if (buf.position() > 0)
+ {
+ // flush encoded chars first...
+ sb.append(decode(buf, decoder));
+ buf.clear();
+ }
+
+ sb.append(ch);
+ }
+ }
+
+ // flush trailing encoded characters...
+ if (buf.position() > 0)
+ {
+ sb.append(decode(buf, decoder));
+ buf.clear();
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Removes all superfluous dot-segments using the algorithm described in RFC-3986 section 5.2.4.
+ *
+ * @param path the path to remove all dot-segments from, can be <code>null</code>.
+ * @return the cleaned path, can be <code>null</code> only if the given path was <code>null</code>.
+ */
+ public static String removeDotSegments(String path)
+ {
+ // Handle special cases...
+ if (path == null)
+ {
+ return null;
+ }
+ if (isEmpty(path))
+ {
+ return "";
+ }
+
+ StringBuilder scratch = new StringBuilder(path);
+ StringBuilder sb = new StringBuilder();
+ char ch, la = 0, laa = 0;
+
+ while (scratch.length() > 0)
+ {
+ int len = scratch.length();
+ ch = scratch.charAt(0);
+ if (ch == '.')
+ {
+ if (len > 1)
+ {
+ la = scratch.charAt(1);
+ }
+ if (la == '.' && (len > 2))
+ {
+ laa = scratch.charAt(2);
+ }
+
+ if (la == '/' || laa == '/')
+ {
+ // Step A: remove '../' or './' from input...
+ scratch.delete(0, (laa == '/') ? 3 : 2);
+ la = laa = 0;
+ continue;
+ }
+ else
+ {
+ // Step D: remove '..' or '.' from input...
+ scratch.delete(0, (laa == '/') ? 2 : 1);
+ la = laa = 0;
+ continue;
+ }
+ }
+ else if (ch == '/')
+ {
+ if (len > 1)
+ {
+ la = scratch.charAt(1);
+ }
+ if (la == '.' && (len > 2))
+ {
+ laa = scratch.charAt(2);
+ }
+
+ if (la == '.' && laa == '.')
+ {
+ // Step C: remove '/../' or '/..' from input...
+ char laaa = (len > 3) ? scratch.charAt(3) : 0;
+ int lastSegment = sb.lastIndexOf(SLASH);
+ scratch.replace(0, laaa == '/' ? 4 : 3, "/");
+ sb.setLength(Math.max(0, lastSegment));
+ la = laa = 0;
+ continue;
+ }
+ if ((la == '.' && laa == '/') || (la == '.' && laa != '/'))
+ {
+ // Step B: remove '/./' or '/.' from input...
+ scratch.replace(0, laa == '/' ? 3 : 2, "/");
+ la = laa = 0;
+ continue;
+ }
+ }
+
+ sb.append(ch);
+ scratch.delete(0, 1);
+ }
+
+ return sb.toString();
+ }
+
+ private static String decode(ByteBuffer bb, CharsetDecoder decoder)
+ {
+ CharBuffer cb = CharBuffer.allocate(128);
+
+ CoderResult result = decoder.decode((ByteBuffer) bb.flip(), cb, true /* endOfInput */);
+ if (result.isError())
+ {
+ throw new IllegalArgumentException("Malformed UTF-8!");
+ }
+
+ return ((CharBuffer) cb.flip()).toString();
+ }
+
+ private static boolean endsWith(CharSequence seq, String part)
+ {
+ int len = part.length();
+ if (seq.length() < len)
+ {
+ return false;
+ }
+ for (int i = 0; i < len; i++)
+ {
+ if (seq.charAt(seq.length() - (i + 1)) != part.charAt(i))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int hexVal(CharSequence seq, int idx)
+ {
+ char ch = seq.charAt(idx);
+ if (ch >= '0' && ch <= '9')
+ {
+ return ch - '0';
+ }
+ else if (ch >= 'a' && ch <= 'f')
+ {
+ return 10 + (ch - 'a');
+ }
+ else if (ch >= 'A' && ch <= 'F')
+ {
+ return 10 + (ch - 'A');
+ }
+ throw new IllegalArgumentException("Invalid hex digit: " + ch);
+ }
+
+ private static boolean isEmpty(String value)
+ {
+ return value == null || "".equals(value.trim());
+ }
+
+ /**
+ * Creates a new {@link UriUtils} instance.
+ */
+ private UriUtils()
+ {
+ // Nop
+ }
+}
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java
index 9651998..5e97af7 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java
@@ -16,12 +16,13 @@
*/
package org.apache.felix.http.base.internal.handler;
-import org.junit.Test;
-import org.junit.Assert;
-import org.mockito.Mockito;
-import org.apache.felix.http.base.internal.context.ExtServletContext;
import java.util.Hashtable;
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
public abstract class AbstractHandlerTest
{
protected ExtServletContext context;
@@ -32,16 +33,16 @@
{
this.context = Mockito.mock(ExtServletContext.class);
}
-
+
@Test
public void testId()
{
AbstractHandler h1 = createHandler();
AbstractHandler h2 = createHandler();
- Assert.assertNotNull(h1.getId());
- Assert.assertNotNull(h2.getId());
- Assert.assertFalse(h1.getId().equals(h2.getId()));
+ Assert.assertTrue(h1.getId() > 0);
+ Assert.assertTrue(h2.getId() > 0);
+ Assert.assertFalse(h1.getId() == h2.getId());
}
@Test
@@ -49,7 +50,7 @@
{
AbstractHandler handler = createHandler();
Assert.assertEquals(0, handler.getInitParams().size());
-
+
Hashtable<String, String> map = new Hashtable<String, String>();
map.put("key1", "value1");
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java
index b1ab850..53da35d 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java
@@ -26,8 +26,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-public class FilterHandlerTest
- extends AbstractHandlerTest
+public class FilterHandlerTest extends AbstractHandlerTest
{
private Filter filter;
@@ -45,7 +44,7 @@
private FilterHandler createHandler(String pattern, int ranking)
{
- return new FilterHandler(this.context, this.filter, pattern, ranking);
+ return new FilterHandler(this.context, this.filter, pattern, ranking, null /* name */);
}
@Test
@@ -83,8 +82,7 @@
}
@Test
- public void testInit()
- throws Exception
+ public void testInit() throws Exception
{
FilterHandler h1 = createHandler("/a", 0);
h1.init();
@@ -100,8 +98,7 @@
}
@Test
- public void testHandleNotFound()
- throws Exception
+ public void testHandleNotFound() throws Exception
{
FilterHandler h1 = createHandler("/a", 0);
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -116,8 +113,7 @@
}
@Test
- public void testHandleFound()
- throws Exception
+ public void testHandleFound() throws Exception
{
FilterHandler h1 = createHandler("/a", 0);
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -133,8 +129,7 @@
}
@Test
- public void testHandleFoundForbidden()
- throws Exception
+ public void testHandleFoundForbidden() throws Exception
{
FilterHandler h1 = createHandler("/a", 0);
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -151,8 +146,7 @@
}
@Test
- public void testHandleNotFoundContextRoot()
- throws Exception
+ public void testHandleNotFoundContextRoot() throws Exception
{
FilterHandler h1 = createHandler("/a", 0);
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -167,8 +161,7 @@
}
@Test
- public void testHandleFoundContextRoot()
- throws Exception
+ public void testHandleFoundContextRoot() throws Exception
{
FilterHandler h1 = createHandler("/", 0);
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java
index 105aaf5..877114d 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java
@@ -16,13 +16,20 @@
*/
package org.apache.felix.http.base.internal.handler;
-import org.mockito.Mockito;
-import org.osgi.service.http.HttpContext;
-import org.junit.Test;
-import org.junit.Assert;
-import org.junit.Before;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
import javax.servlet.http.HttpServletRequest;
+import org.apache.felix.http.base.internal.context.ServletContextImpl;
+import org.apache.felix.http.base.internal.dispatch.Dispatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.service.http.HttpContext;
+
public class ServletHandlerRequestTest
{
private HttpServletRequest superReq1;
@@ -38,106 +45,112 @@
@Before
public void setUp()
{
- this.superReq1 = Mockito.mock(HttpServletRequest.class);
- Mockito.when(this.superReq1.getContextPath()).thenReturn("/mycontext");
- Mockito.when(this.superReq1.getServletPath()).thenReturn("");
- Mockito.when(this.superReq1.getRequestURI()).thenReturn("/mycontext/request/to/resource");
- Mockito.when(this.superReq1.getPathInfo()).thenReturn("/request/to/resource");
- Mockito.when(this.superReq1.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
- Mockito.when(this.superReq1.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
- this.req1 = new ServletHandlerRequest(this.superReq1, "/");
+ ServletContextImpl context = mock(ServletContextImpl.class);
- this.superReq2 = Mockito.mock(HttpServletRequest.class);
- Mockito.when(this.superReq2.getContextPath()).thenReturn("/mycontext");
- Mockito.when(this.superReq2.getServletPath()).thenReturn("");
- Mockito.when(this.superReq2.getRequestURI()).thenReturn("/mycontext/myservlet/request/to/resource;jsession=123");
- Mockito.when(this.superReq2.getPathInfo()).thenReturn("/myservlet/request/to/resource");
- Mockito.when(this.superReq2.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
- Mockito.when(this.superReq2.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
- Mockito.when(this.superReq2.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
- Mockito.when(this.superReq2.getRemoteUser()).thenReturn("sling");
- this.req2 = new ServletHandlerRequest(this.superReq2, "/myservlet");
+ this.superReq1 = mock(HttpServletRequest.class);
+ when(this.superReq1.getContextPath()).thenReturn("/mycontext");
+ when(this.superReq1.getServletPath()).thenReturn("");
+ when(this.superReq1.getRequestURI()).thenReturn("/mycontext/request/to/resource");
+ when(this.superReq1.getPathInfo()).thenReturn("/request/to/resource");
+ when(this.superReq1.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
+ when(this.superReq1.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
+ this.req1 = new ServletHandlerRequest(this.superReq1, context, "/");
- this.superReq3 = Mockito.mock(HttpServletRequest.class);
- Mockito.when(this.superReq3.getContextPath()).thenReturn("/mycontext");
- Mockito.when(this.superReq3.getServletPath()).thenReturn("/proxyservlet");
- Mockito.when(this.superReq3.getRequestURI()).thenReturn("/mycontext/proxyservlet/request/to/resource");
- Mockito.when(this.superReq3.getPathInfo()).thenReturn("/request/to/resource");
- Mockito.when(this.superReq3.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
- Mockito.when(this.superReq3.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
- this.req3 = new ServletHandlerRequest(this.superReq3, "/");
+ this.superReq2 = mock(HttpServletRequest.class);
+ when(this.superReq2.getContextPath()).thenReturn("/mycontext");
+ when(this.superReq2.getServletPath()).thenReturn("");
+ when(this.superReq2.getRequestURI()).thenReturn("/mycontext/myservlet/request/to/resource;jsession=123");
+ when(this.superReq2.getPathInfo()).thenReturn("/myservlet/request/to/resource");
+ when(this.superReq2.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
+ when(this.superReq2.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
+ when(this.superReq2.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
+ when(this.superReq2.getRemoteUser()).thenReturn("sling");
+ this.req2 = new ServletHandlerRequest(this.superReq2, context, "/myservlet");
- this.superReq4 = Mockito.mock(HttpServletRequest.class);
- Mockito.when(this.superReq4.getContextPath()).thenReturn("/mycontext");
- Mockito.when(this.superReq4.getServletPath()).thenReturn("/proxyservlet");
- Mockito.when(this.superReq4.getRequestURI()).thenReturn("/mycontext/proxyservlet/myservlet/request/to/resource;jsession=123");
- Mockito.when(this.superReq4.getPathInfo()).thenReturn("/myservlet/request/to/resource");
- Mockito.when(this.superReq4.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
- Mockito.when(this.superReq4.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
- Mockito.when(this.superReq4.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
- Mockito.when(this.superReq4.getRemoteUser()).thenReturn("sling");
- this.req4 = new ServletHandlerRequest(this.superReq4, "/myservlet");
+ this.superReq3 = mock(HttpServletRequest.class);
+ when(this.superReq3.getContextPath()).thenReturn("/mycontext");
+ when(this.superReq3.getServletPath()).thenReturn("/proxyservlet");
+ when(this.superReq3.getRequestURI()).thenReturn("/mycontext/proxyservlet/request/to/resource");
+ when(this.superReq3.getPathInfo()).thenReturn("/request/to/resource");
+ when(this.superReq3.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
+ when(this.superReq3.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
+ this.req3 = new ServletHandlerRequest(this.superReq3, context, "/");
+
+ this.superReq4 = mock(HttpServletRequest.class);
+ when(this.superReq4.getContextPath()).thenReturn("/mycontext");
+ when(this.superReq4.getServletPath()).thenReturn("/proxyservlet");
+ when(this.superReq4.getRequestURI()).thenReturn("/mycontext/proxyservlet/myservlet/request/to/resource;jsession=123");
+ when(this.superReq4.getPathInfo()).thenReturn("/myservlet/request/to/resource");
+ when(this.superReq4.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
+ when(this.superReq4.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
+ when(this.superReq4.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
+ when(this.superReq4.getRemoteUser()).thenReturn("sling");
+ this.req4 = new ServletHandlerRequest(this.superReq4, context, "/myservlet");
}
@Test
public void testPathInfo()
{
- Assert.assertEquals("/request/to/resource", this.req1.getPathInfo());
- Assert.assertEquals("/request/to/resource", this.req2.getPathInfo());
- Assert.assertEquals("/request/to/resource", this.req3.getPathInfo());
- Assert.assertEquals("/request/to/resource", this.req4.getPathInfo());
+ assertEquals("/request/to/resource", this.req1.getPathInfo());
+ assertEquals("/request/to/resource", this.req2.getPathInfo());
+ assertEquals("/request/to/resource", this.req3.getPathInfo());
+ assertEquals("/request/to/resource", this.req4.getPathInfo());
}
@Test
public void testSuperGetServletPath()
{
- Assert.assertEquals("", this.superReq1.getServletPath());
- Assert.assertEquals("", this.superReq2.getServletPath());
- Assert.assertEquals("/proxyservlet", this.superReq3.getServletPath());
- Assert.assertEquals("/proxyservlet", this.superReq4.getServletPath());
+ assertEquals("", this.superReq1.getServletPath());
+ assertEquals("", this.superReq2.getServletPath());
+ assertEquals("/proxyservlet", this.superReq3.getServletPath());
+ assertEquals("/proxyservlet", this.superReq4.getServletPath());
}
@Test
public void testServletPath()
{
- Assert.assertEquals("", this.req1.getServletPath());
- Assert.assertEquals("/myservlet", this.req2.getServletPath());
- Assert.assertEquals("", this.req3.getServletPath());
- Assert.assertEquals("/myservlet", this.req4.getServletPath());
+ assertEquals("", this.req1.getServletPath());
+ assertEquals("/myservlet", this.req2.getServletPath());
+ assertEquals("", this.req3.getServletPath());
+ assertEquals("/myservlet", this.req4.getServletPath());
}
@Test
public void testContextPath()
{
- Assert.assertEquals("/mycontext", this.req1.getContextPath());
- Assert.assertEquals("/mycontext", this.req2.getContextPath());
- Assert.assertEquals("/mycontext/proxyservlet", this.req3.getContextPath());
- Assert.assertEquals("/mycontext/proxyservlet", this.req4.getContextPath());
+ assertEquals("/mycontext", this.req1.getContextPath());
+ assertEquals("/mycontext", this.req2.getContextPath());
+ assertEquals("/mycontext/proxyservlet", this.req3.getContextPath());
+ assertEquals("/mycontext/proxyservlet", this.req4.getContextPath());
}
@Test
public void testGetAuthType()
{
- Assert.assertEquals(HttpServletRequest.BASIC_AUTH, this.req1.getAuthType());
- Mockito.verify(this.superReq1).getAttribute(HttpContext.AUTHENTICATION_TYPE);
- Mockito.verifyNoMoreInteractions(this.superReq1);
+ assertEquals(HttpServletRequest.BASIC_AUTH, this.req1.getAuthType());
+ verify(this.superReq1).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+ verify(this.superReq1).getAttribute(HttpContext.AUTHENTICATION_TYPE);
+ verifyNoMoreInteractions(this.superReq1);
- Assert.assertEquals(HttpServletRequest.DIGEST_AUTH, this.req2.getAuthType());
- Mockito.verify(this.superReq2).getAttribute(HttpContext.AUTHENTICATION_TYPE);
- Mockito.verify(this.superReq2).getAuthType();
- Mockito.verifyNoMoreInteractions(this.superReq2);
+ assertEquals(HttpServletRequest.DIGEST_AUTH, this.req2.getAuthType());
+ verify(this.superReq2).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+ verify(this.superReq2).getAttribute(HttpContext.AUTHENTICATION_TYPE);
+ verify(this.superReq2).getAuthType();
+ verifyNoMoreInteractions(this.superReq2);
}
@Test
public void testGetRemoteUser()
{
- Assert.assertEquals("felix", this.req1.getRemoteUser());
- Mockito.verify(this.superReq1).getAttribute(HttpContext.REMOTE_USER);
- Mockito.verifyNoMoreInteractions(this.superReq1);
+ assertEquals("felix", this.req1.getRemoteUser());
+ verify(this.superReq1).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+ verify(this.superReq1).getAttribute(HttpContext.REMOTE_USER);
+ verifyNoMoreInteractions(this.superReq1);
- Assert.assertEquals("sling", this.req2.getRemoteUser());
- Mockito.verify(this.superReq2).getAttribute(HttpContext.REMOTE_USER);
- Mockito.verify(this.superReq2).getRemoteUser();
- Mockito.verifyNoMoreInteractions(this.superReq2);
+ assertEquals("sling", this.req2.getRemoteUser());
+ verify(this.superReq2).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+ verify(this.superReq2).getAttribute(HttpContext.REMOTE_USER);
+ verify(this.superReq2).getRemoteUser();
+ verifyNoMoreInteractions(this.superReq2);
}
}
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java
index abb49b4..f1f9626 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java
@@ -16,17 +16,17 @@
*/
package org.apache.felix.http.base.internal.handler;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.Assert;
-import org.mockito.Mockito;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-public class ServletHandlerTest
- extends AbstractHandlerTest
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ServletHandlerTest extends AbstractHandlerTest
{
private Servlet servlet;
@@ -44,7 +44,7 @@
private ServletHandler createHandler(String alias)
{
- return new ServletHandler(this.context, this.servlet, alias);
+ return new ServletHandler(this.context, this.servlet, alias, null /* name */);
}
@Test
@@ -71,8 +71,7 @@
}
@Test
- public void testInit()
- throws Exception
+ public void testInit() throws Exception
{
ServletHandler h1 = createHandler("/a");
h1.init();
@@ -88,8 +87,7 @@
}
@Test
- public void testHandleNotFound()
- throws Exception
+ public void testHandleNotFound() throws Exception
{
ServletHandler h1 = createHandler("/a");
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -103,8 +101,7 @@
}
@Test
- public void testHandleFound()
- throws Exception
+ public void testHandleFound() throws Exception
{
ServletHandler h1 = createHandler("/a");
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -115,13 +112,11 @@
boolean result = h1.handle(req, res);
Assert.assertTrue(result);
- Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class),
- Mockito.any(HttpServletResponse.class));
+ Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class), Mockito.any(HttpServletResponse.class));
}
@Test
- public void testHandleFoundForbidden()
- throws Exception
+ public void testHandleFoundForbidden() throws Exception
{
ServletHandler h1 = createHandler("/a");
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -137,8 +132,7 @@
}
@Test
- public void testHandleNotFoundContextRoot()
- throws Exception
+ public void testHandleNotFoundContextRoot() throws Exception
{
ServletHandler h1 = createHandler("/a");
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -153,8 +147,7 @@
}
@Test
- public void testHandleFoundContextRoot()
- throws Exception
+ public void testHandleFoundContextRoot() throws Exception
{
ServletHandler h1 = createHandler("/");
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -165,7 +158,6 @@
boolean result = h1.handle(req, res);
Assert.assertTrue(result);
- Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class),
- Mockito.any(HttpServletResponse.class));
+ Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class), Mockito.any(HttpServletResponse.class));
}
}
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/util/UriUtilsTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/util/UriUtilsTest.java
new file mode 100644
index 0000000..9d6ad2e
--- /dev/null
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/util/UriUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.base.internal.util;
+
+import static org.apache.felix.http.base.internal.util.UriUtils.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Test cases for {@link UriUtils}.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class UriUtilsTest
+{
+ @Test
+ public void testConcatOk()
+ {
+ assertEquals(null, concat(null, null));
+ assertEquals("", concat(null, ""));
+ assertEquals("/", concat(null, "/"));
+ assertEquals("foo", concat(null, "foo"));
+ assertEquals("/foo", concat(null, "/foo"));
+
+ assertEquals("", concat("", null));
+ assertEquals("/", concat("/", null));
+ assertEquals("foo", concat("foo", null));
+ assertEquals("/foo", concat("/foo", null));
+
+ assertEquals("", concat("", ""));
+ assertEquals("foo", concat("", "foo"));
+ assertEquals("/", concat("", "/"));
+ assertEquals("/foo", concat("", "/foo"));
+
+ assertEquals("foo", concat("foo", ""));
+ assertEquals("/", concat("/", ""));
+ assertEquals("/foo", concat("/foo", ""));
+
+ assertEquals("foo", concat("foo", ""));
+ assertEquals("foo/bar", concat("foo", "bar"));
+ assertEquals("foo/", concat("foo", "/"));
+ assertEquals("foo/bar", concat("foo", "/bar"));
+
+ assertEquals("/foo", concat("/", "foo"));
+ assertEquals("/", concat("/", "/"));
+ assertEquals("/bar", concat("/", "/bar"));
+
+ assertEquals("foo/", concat("foo/", null));
+ assertEquals("foo/", concat("foo/", ""));
+ assertEquals("foo/bar", concat("foo/", "bar"));
+ assertEquals("foo/", concat("foo/", "/"));
+ assertEquals("foo/bar", concat("foo/", "/bar"));
+
+ assertEquals("?quu=1", concat("?quu=1", null));
+ assertEquals("?quu=1", concat("?quu=1", ""));
+ assertEquals("foo?quu=1", concat("?quu=1", "foo"));
+ assertEquals("/?quu=1", concat("?quu=1", "/"));
+ assertEquals("/foo?quu=1", concat("?quu=1", "/foo"));
+
+ assertEquals("foo?quu=1", concat("foo?quu=1", null));
+ assertEquals("foo?quu=1", concat("foo?quu=1", ""));
+ assertEquals("foo/bar?quu=1", concat("foo?quu=1", "bar"));
+ assertEquals("foo/?quu=1", concat("foo?quu=1", "/"));
+ assertEquals("foo/bar?quu=1", concat("foo?quu=1", "/bar"));
+
+ assertEquals("foo/?quu=1", concat("foo/?quu=1", null));
+ assertEquals("foo/?quu=1", concat("foo/?quu=1", ""));
+ assertEquals("foo/bar?quu=1", concat("foo/?quu=1", "bar"));
+ assertEquals("foo/?quu=1", concat("foo/?quu=1", "/"));
+ assertEquals("foo/bar?quu=1", concat("foo/?quu=1", "/bar"));
+ }
+
+ @Test
+ public void testDecodePathOk()
+ {
+ assertEquals(null, decodePath(null));
+ assertEquals("foo bar", decodePath("foo%20bar"));
+ assertEquals("foo%23;,:=b a r", decodePath("foo%2523%3b%2c:%3db%20a%20r"));
+ assertEquals("f\u00e4\u00e4%23;,:=b a r=", decodePath("f\u00e4\u00e4%2523%3b%2c:%3db%20a%20r%3D"));
+ assertEquals("f\u0629\u0629%23;,:=b a r", decodePath("f%d8%a9%d8%a9%2523%3b%2c:%3db%20a%20r"));
+ }
+
+ @Test
+ public void testRemoveDotSegmentsOk()
+ {
+ assertEquals(null, removeDotSegments(null));
+ assertEquals("", removeDotSegments(""));
+ assertEquals("/", removeDotSegments("/"));
+ assertEquals("foo", removeDotSegments("./foo"));
+ assertEquals("/bar/", removeDotSegments("./foo/../bar/"));
+ assertEquals("foo", removeDotSegments("../foo"));
+ assertEquals("/", removeDotSegments("/foo/.."));
+ assertEquals("/foo/", removeDotSegments("/foo/."));
+ assertEquals("/foo/bar", removeDotSegments("/foo/./bar"));
+ assertEquals("/bar", removeDotSegments("/foo/../bar"));
+ assertEquals("/bar", removeDotSegments("/foo/./../bar"));
+ assertEquals("/bar//", removeDotSegments("/foo/./../bar//"));
+ assertEquals("/", removeDotSegments("/foo/../bar/.."));
+ assertEquals("/foo/quu", removeDotSegments("/foo/bar/qux/./../../quu"));
+ assertEquals("mid/6", removeDotSegments("mid/content=5/../6"));
+ }
+}
diff --git a/http/itest/pom.xml b/http/itest/pom.xml
index 708876c..73b7c2d 100644
--- a/http/itest/pom.xml
+++ b/http/itest/pom.xml
@@ -72,6 +72,11 @@
<artifactId>org.apache.felix.http.jetty</artifactId>
<version>${felix.http.api.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.configadmin</artifactId>
+ <version>1.8.0</version>
+ </dependency>
<dependency>
<groupId>junit</groupId>
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
new file mode 100644
index 0000000..8eb6ec7
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.itest;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class AsyncTest extends BaseIntegrationTest
+{
+
+ /**
+ * Tests that we can use an asynchronous servlet (introduced in Servlet 3.0 spec).
+ */
+// @Test
+ public void testAsyncServletOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ final AsyncContext asyncContext = req.startAsync(req, resp);
+ asyncContext.setTimeout(2000);
+ asyncContext.start(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ // Simulate a long running process...
+ Thread.sleep(1000);
+
+ HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
+ response.setStatus(SC_OK);
+ response.getWriter().printf("Hello Async world!");
+
+ asyncContext.complete();
+ }
+ catch (InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ };
+
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("Hello Async world!", createURL("/test"));
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_NOT_FOUND, createURL("/test"));
+ }
+
+ /**
+ * Tests that we can use an asynchronous servlet (introduced in Servlet 3.0 spec) using the dispatching functionality.
+ */
+ @Test
+ public void testAsyncServletWithDispatchOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ DispatcherType dispatcherType = req.getDispatcherType();
+ if (DispatcherType.REQUEST == dispatcherType)
+ {
+ final AsyncContext asyncContext = req.startAsync(req, resp);
+ asyncContext.start(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ // Simulate a long running process...
+ Thread.sleep(1000);
+
+ asyncContext.getRequest().setAttribute("msg", "Hello Async world!");
+ asyncContext.dispatch();
+ }
+ catch (InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+ }
+ else if (DispatcherType.ASYNC == dispatcherType)
+ {
+ String response = (String) req.getAttribute("msg");
+ resp.setStatus(SC_OK);
+ resp.getWriter().printf(response);
+ resp.flushBuffer();
+ }
+ }
+ };
+
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("Hello Async world!", createURL("/test"));
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_NOT_FOUND, createURL("/test"));
+ }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
index 8fa35ac..37b2cce 100644
--- a/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
import static org.ops4j.pax.exam.Constants.START_LEVEL_TEST_BUNDLE;
import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage;
@@ -37,8 +38,11 @@
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.servlet.Filter;
@@ -60,6 +64,10 @@
import org.ops4j.pax.exam.junit.Configuration;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
@@ -144,7 +152,7 @@
}
@Override
- protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setStatus(HttpServletResponse.SC_OK);
}
@@ -224,6 +232,18 @@
}
}
+ protected static Dictionary<String, ?> createDictionary(Object... entries)
+ {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ for (int i = 0; i < entries.length; i += 2)
+ {
+ String key = (String) entries[i];
+ Object value = entries[i + 1];
+ props.put(key, value);
+ }
+ return props;
+ }
+
protected static URL createURL(String path)
{
if (path == null)
@@ -295,6 +315,7 @@
url("link:classpath:META-INF/links/org.apache.geronimo.specs.atinject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
mavenBundle("org.apache.felix", ORG_APACHE_FELIX_HTTP_JETTY).versionAsInProject().startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ mavenBundle("org.apache.felix", "org.apache.felix.configadmin").versionAsInProject().startLevel(START_LEVEL_SYSTEM_BUNDLES),
junitBundles(), frameworkStartLevel(START_LEVEL_TEST_BUNDLE), felix());
}
@@ -339,6 +360,64 @@
return result;
}
+ protected org.osgi.service.cm.Configuration configureHttpService(Dictionary<?, ?> props) throws Exception
+ {
+ final String pid = "org.apache.felix.http";
+ ServiceTracker tracker = new ServiceTracker(m_context, ConfigurationAdmin.class.getName(), null);
+ tracker.open();
+
+ ServiceRegistration reg = null;
+ org.osgi.service.cm.Configuration config = null;
+
+ try
+ {
+ ConfigurationAdmin configAdmin = (ConfigurationAdmin) tracker.waitForService(TimeUnit.SECONDS.toMillis(5));
+ assertNotNull("No configuration admin service found?!", configAdmin);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final int configEvent = (props != null) ? ConfigurationEvent.CM_UPDATED : ConfigurationEvent.CM_DELETED;
+
+ config = configAdmin.getConfiguration(pid, null);
+
+ reg = m_context.registerService(ConfigurationListener.class.getName(), new ConfigurationListener()
+ {
+ @Override
+ public void configurationEvent(ConfigurationEvent event)
+ {
+ if (pid.equals(event.getPid()) && event.getType() == configEvent)
+ {
+ latch.countDown();
+ }
+ }
+ }, null);
+
+ if (props != null)
+ {
+ config.update(props);
+ }
+ else
+ {
+ config.delete();
+ }
+
+ assertTrue("Configuration not provisioned in time!", latch.await(5, TimeUnit.SECONDS));
+
+ // Needed to get Jetty restarted...
+ TimeUnit.MILLISECONDS.sleep(150);
+
+ return config;
+ }
+ finally
+ {
+ if (reg != null)
+ {
+ reg.unregister();
+ }
+
+ tracker.close();
+ }
+ }
+
/**
* @param bsn
* @return
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/RequestDispatchTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/RequestDispatchTest.java
new file mode 100644
index 0000000..6630525
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/RequestDispatchTest.java
@@ -0,0 +1,514 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.felix.http.itest;
+
+import static javax.servlet.RequestDispatcher.FORWARD_CONTEXT_PATH;
+import static javax.servlet.RequestDispatcher.FORWARD_PATH_INFO;
+import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.FORWARD_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.FORWARD_SERVLET_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_CONTEXT_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_PATH_INFO;
+import static javax.servlet.RequestDispatcher.INCLUDE_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.http.NamespaceException;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class RequestDispatchTest extends BaseIntegrationTest
+{
+ /**
+ * Tests that we can forward content from other servlets using the {@link RequestDispatcher} service.
+ */
+ @Test
+ public void testDispatchForwardToAbsoluteURIOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(2);
+ CountDownLatch destroyLatch = new CountDownLatch(2);
+
+ TestServlet forward = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ Object includeContextPath = req.getAttribute(FORWARD_CONTEXT_PATH);
+ if (includeContextPath != null)
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/forward", req.getServletPath());
+ assertEquals(null, req.getPathInfo());
+ assertEquals("/forward", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ assertEquals("", includeContextPath);
+ assertEquals("/test", req.getAttribute(FORWARD_SERVLET_PATH));
+ assertEquals("/foo", req.getAttribute(FORWARD_PATH_INFO));
+ assertEquals("/test/foo", req.getAttribute(FORWARD_REQUEST_URI));
+ assertEquals("bar=qux&quu", req.getAttribute(FORWARD_QUERY_STRING));
+ }
+ else
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/forward", req.getServletPath());
+ assertEquals("/bar", req.getPathInfo());
+ assertEquals("/forward/bar", req.getRequestURI());
+ assertEquals("quu=qux", req.getQueryString());
+ }
+
+ resp.getWriter().println("FORWARD");
+ }
+ };
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ resp.getWriter().println("NOT_SEND");
+ req.getRequestDispatcher("/forward").forward(req, resp);
+ resp.getWriter().println("NOT_SEND");
+ }
+ };
+
+ register("/forward", forward);
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("FORWARD\n", createURL("/test/foo?bar=qux&quu"));
+ assertContent("FORWARD\n", createURL("/forward/bar?quu=qux"));
+
+ unregister(forward);
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Tests that we can forward content from other servlets using the {@link RequestDispatcher} service.
+ */
+ @Test
+ public void testDispatchForwardToRelativeURIOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ Object contextPathAttr = req.getAttribute(FORWARD_CONTEXT_PATH);
+ if (contextPathAttr != null)
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/test/forward", req.getPathInfo()); // XXX ?
+ assertEquals("/test/forward", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ assertEquals("", contextPathAttr);
+ assertEquals("/test", req.getAttribute(FORWARD_SERVLET_PATH));
+ assertEquals("/foo", req.getAttribute(FORWARD_PATH_INFO));
+ assertEquals("/test/foo", req.getAttribute(FORWARD_REQUEST_URI));
+ assertEquals("bar=qux&quu", req.getAttribute(FORWARD_QUERY_STRING));
+
+ resp.getWriter().println("FORWARD");
+ }
+ else
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ resp.getWriter().println("NOT_SEND");
+
+ // ServletContext#getRequestDispatcher only takes absolute paths...
+ RequestDispatcher disp = req.getServletContext().getRequestDispatcher("forward");
+ assertNull("ServletContext returned RequestDispatcher for relative path?!", disp);
+ // Causes a request to ourselves being made (/test/forward)...
+ disp = req.getRequestDispatcher("forward");
+ assertNotNull("ServletRequest returned NO RequestDispatcher for relative path?!", disp);
+
+ disp.forward(req, resp);
+ resp.getWriter().println("NOT_SEND");
+ }
+ }
+ };
+
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("FORWARD\n", createURL("/test/foo?bar=qux&quu"));
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Tests that we can include content from other servlets using the {@link RequestDispatcher} service.
+ */
+ @Test
+ public void testDispatchIncludeAbsoluteURIOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(2);
+ CountDownLatch destroyLatch = new CountDownLatch(2);
+
+ TestServlet include = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ Object includeContextPath = req.getAttribute(INCLUDE_CONTEXT_PATH);
+ if (includeContextPath != null)
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ assertEquals("", includeContextPath);
+ assertEquals("/include", req.getAttribute(INCLUDE_SERVLET_PATH));
+ assertEquals(null, req.getAttribute(INCLUDE_PATH_INFO));
+ assertEquals("/include", req.getAttribute(INCLUDE_REQUEST_URI));
+ assertEquals(null, req.getAttribute(INCLUDE_QUERY_STRING));
+ }
+ else
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/include", req.getServletPath());
+ assertEquals("/bar", req.getPathInfo());
+ assertEquals("/include/bar", req.getRequestURI());
+ assertEquals("quu=qux", req.getQueryString());
+ }
+
+ resp.getWriter().println("INCLUDE");
+ }
+ };
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ resp.getWriter().println("BEFORE");
+ req.getRequestDispatcher("/include").include(req, resp);
+ resp.getWriter().println("AFTER");
+ }
+ };
+
+ register("/include", include);
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("BEFORE\nINCLUDE\nAFTER\n", createURL("/test/foo?bar=qux&quu"));
+ assertContent("INCLUDE\n", createURL("/include/bar?quu=qux"));
+
+ unregister(include);
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Tests that we can include content from other servlets using the {@link RequestDispatcher} service.
+ */
+ @Test
+ public void testDispatchIncludeRelativeURIOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ Object contextPathAttr = req.getAttribute(INCLUDE_CONTEXT_PATH);
+ if (contextPathAttr != null)
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ assertEquals("", contextPathAttr);
+ assertEquals("/test", req.getAttribute(INCLUDE_SERVLET_PATH));
+ // assertEquals("/include", req.getAttribute(INCLUDE_PATH_INFO));
+ // assertEquals("/test/include", req.getAttribute(INCLUDE_REQUEST_URI));
+ assertEquals(null, req.getAttribute(INCLUDE_QUERY_STRING));
+
+ resp.getWriter().println("INCLUDE");
+ }
+ else
+ {
+ assertEquals("", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ // assertEquals("/foo", req.getPathInfo());
+ // assertEquals("/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ resp.getWriter().println("BEFORE");
+
+ // ServletContext#getRequestDispatcher only takes absolute paths...
+ RequestDispatcher disp = req.getServletContext().getRequestDispatcher("include");
+ assertNull("ServletContext returned RequestDispatcher for relative path?!", disp);
+ // Causes a request to ourselves being made (/test/forward)...
+ disp = req.getRequestDispatcher("include");
+ assertNotNull("ServletRequest returned NO RequestDispatcher for relative path?!", disp);
+
+ disp.include(req, resp);
+ resp.getWriter().println("AFTER");
+ }
+ }
+ };
+
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("BEFORE\nINCLUDE\nAFTER\n", createURL("/test/foo?bar=qux&quu"));
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Tests that we can forward content from other servlets using the {@link RequestDispatcher} service.
+ */
+ @Test
+ public void testDispatchOnNonRootContextPathOk() throws Exception
+ {
+ // Configure HTTP on a different context path...
+ Configuration config = configureHttpService(createDictionary("org.apache.felix.http.context_path", "/context", "org.osgi.service.http.port", "8080"));
+
+ try
+ {
+ // Include two tests in one as to keep tests a little easier to read...
+ doTestForwardAbsoluteURI();
+ doTestIncludeAbsoluteURI();
+ }
+ finally
+ {
+ config.delete();
+ }
+ }
+
+ private void doTestForwardAbsoluteURI() throws ServletException, NamespaceException, InterruptedException, IOException
+ {
+ CountDownLatch initLatch = new CountDownLatch(2);
+ CountDownLatch destroyLatch = new CountDownLatch(2);
+
+ TestServlet forward = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ Object includeContextPath = req.getAttribute(FORWARD_CONTEXT_PATH);
+ if (includeContextPath != null)
+ {
+ assertEquals("/context", req.getContextPath());
+ assertEquals("/forward", req.getServletPath());
+ assertEquals(null, req.getPathInfo());
+ assertEquals("/context/forward", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ assertEquals("/context", includeContextPath);
+ assertEquals("/test", req.getAttribute(FORWARD_SERVLET_PATH));
+ assertEquals("/foo", req.getAttribute(FORWARD_PATH_INFO));
+ assertEquals("/context/test/foo", req.getAttribute(FORWARD_REQUEST_URI));
+ assertEquals("bar=qux&quu", req.getAttribute(FORWARD_QUERY_STRING));
+ }
+ else
+ {
+ assertEquals("/context", req.getContextPath());
+ assertEquals("/forward", req.getServletPath());
+ assertEquals("/bar", req.getPathInfo());
+ assertEquals("/context/forward/bar", req.getRequestURI());
+ assertEquals("quu=qux", req.getQueryString());
+ }
+
+ resp.getWriter().println("FORWARD");
+ }
+ };
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ assertEquals("/context", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/context/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ resp.getWriter().println("NOT_SEND");
+ req.getRequestDispatcher("/forward").forward(req, resp);
+ resp.getWriter().println("NOT_SEND");
+ }
+ };
+
+ register("/forward", forward);
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("FORWARD\n", createURL("/context/test/foo?bar=qux&quu"));
+ assertContent("FORWARD\n", createURL("/context/forward/bar?quu=qux"));
+
+ unregister(forward);
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Tests that we can include content from other servlets using the {@link RequestDispatcher} service.
+ */
+ private void doTestIncludeAbsoluteURI() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(2);
+ CountDownLatch destroyLatch = new CountDownLatch(2);
+
+ TestServlet include = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ Object includeContextPath = req.getAttribute(INCLUDE_CONTEXT_PATH);
+ if (includeContextPath != null)
+ {
+ assertEquals("/context", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/context/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ assertEquals("/context", includeContextPath);
+ assertEquals("/include", req.getAttribute(INCLUDE_SERVLET_PATH));
+ assertEquals(null, req.getAttribute(INCLUDE_PATH_INFO));
+ assertEquals("/context/include", req.getAttribute(INCLUDE_REQUEST_URI));
+ assertEquals(null, req.getAttribute(INCLUDE_QUERY_STRING));
+ }
+ else
+ {
+ assertEquals("/context", req.getContextPath());
+ assertEquals("/include", req.getServletPath());
+ assertEquals("/bar", req.getPathInfo());
+ assertEquals("/context/include/bar", req.getRequestURI());
+ assertEquals("quu=qux", req.getQueryString());
+ }
+
+ resp.getWriter().println("INCLUDE");
+ }
+ };
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ assertEquals("/context", req.getContextPath());
+ assertEquals("/test", req.getServletPath());
+ assertEquals("/foo", req.getPathInfo());
+ assertEquals("/context/test/foo", req.getRequestURI());
+ assertEquals("bar=qux&quu", req.getQueryString());
+
+ resp.getWriter().println("BEFORE");
+ req.getRequestDispatcher("/include").include(req, resp);
+ resp.getWriter().println("AFTER");
+ }
+ };
+
+ register("/include", include);
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("BEFORE\nINCLUDE\nAFTER\n", createURL("/context/test/foo?bar=qux&quu"));
+ assertContent("INCLUDE\n", createURL("/context/include/bar?quu=qux"));
+
+ unregister(include);
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+}