Author: woonsan
Date: Mon Oct 13 11:32:31 2014
New Revision: 1631349
URL: http://svn.apache.org/r1631349
Log:
More extensibility in dealing with proxy commands in a chain and http
client/context creation for more advanced use cases such as sso proxy portlets
(see JS2-1305, etc.)
Added:
portals/applications/webcontent/trunk/reverse-proxy/src/test/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChainIndexingAdditionRemovalTest.java
Modified:
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/WebContentPortlet.java
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/proxy/SimpleReverseProxyPortlet.java
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/command/InitHttpRequestCommand.java
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/filter/SimpleReverseProxyFilter.java
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChain.java
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/servlet/SimpleReverseProxyServlet.java
Modified:
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/WebContentPortlet.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/WebContentPortlet.java?rev=1631349&r1=1631348&r2=1631349&view=diff
==============================================================================
---
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/WebContentPortlet.java
(original)
+++
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/WebContentPortlet.java
Mon Oct 13 11:32:31 2014
@@ -61,6 +61,7 @@ import org.apache.http.client.methods.Cl
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
@@ -380,7 +381,7 @@ public class WebContentPortlet extends G
httpRequest = createHttpRequest(httpClient, methodName,
sourceAttr, sourceParams, null, request);
}
- byte[] result = doPreemptiveAuthentication(httpClient,
httpRequest, request, response);
+ byte[] result = doPreemptiveAuthentication(httpClient,
cookieStore, httpRequest, request, response);
// ...get, cache, and return the content
if (result == null)
@@ -434,8 +435,16 @@ public class WebContentPortlet extends G
try
{
- // ...set up URL and HttpClient stuff
- httpResponse = httpClient.execute(httpRequest);
+ HttpClientContext httpClientContext =
getHttpClientContext(request, httpRequest);
+
+ if (httpClientContext == null)
+ {
+ httpResponse = httpClient.execute(httpRequest);
+ }
+ else
+ {
+ httpResponse = httpClient.execute(httpRequest,
httpClientContext);
+ }
// ...reset base URL with fully resolved path (e.g. if a
directory, path will end with a /, which it may not have in the call to this
method)
rewriter.setBaseUrl( rewriter.getBaseRelativeUrl(
httpRequest.getURI().getPath() )) ;
@@ -474,7 +483,7 @@ public class WebContentPortlet extends G
{
if ( responseCode == 401 )
{
- if (retryCount++ < 1 && doRequestedAuthentication(
httpClient, httpRequest, request, response))
+ if (retryCount++ < 1 &&
doRequestedAuthentication(httpClient, cookieStore, httpRequest, request,
response))
{
// try again, now that we are authorizied
return doHttpWebContent(httpClient, cookieStore,
httpRequest, retryCount, request, response, rewriter);
@@ -562,13 +571,13 @@ public class WebContentPortlet extends G
}
}
- protected byte[] doPreemptiveAuthentication(CloseableHttpClient clent,
HttpRequestBase httpRequest, RenderRequest request, RenderResponse response)
+ protected byte[] doPreemptiveAuthentication(CloseableHttpClient client,
CookieStore cookieStore, HttpRequestBase httpRequest, RenderRequest request,
RenderResponse response)
{
// derived class responsibilty - return true, if credentials have been
set
return null ;
}
-
- protected boolean doRequestedAuthentication(CloseableHttpClient clent,
HttpRequestBase httpRequest, RenderRequest request, RenderResponse response)
+
+ protected boolean doRequestedAuthentication(CloseableHttpClient client,
CookieStore cookieStore, HttpRequestBase httpRequest, RenderRequest request,
RenderResponse response)
{
// derived class responsibilty - return true, if credentials have been
set
return false ;
@@ -594,10 +603,7 @@ public class WebContentPortlet extends G
return rewriter;
}
- protected CloseableHttpClient getHttpClient(RenderRequest request,
CookieStore cookieStore) throws IOException
- {
- // derived class hook (e.g. to set up Basic Authentication)
-
+ protected HttpClientBuilder getHttpClientBuilder(PortletRequest request,
CookieStore cookieStore) {
HttpClientBuilder builder =
HttpClients.custom()
.setRedirectStrategy(new LaxRedirectStrategy());
@@ -633,9 +639,26 @@ public class WebContentPortlet extends G
builder.setDefaultCookieStore(cookieStore);
}
+ return builder;
+ }
+
+ protected CloseableHttpClient getHttpClient(RenderRequest request,
CookieStore cookieStore) throws IOException
+ {
+ HttpClientBuilder builder = getHttpClientBuilder(request, cookieStore);
return builder.build();
}
+ /**
+ * Override this method to give a custom <code>HttpClientContext</code>
+ * when executing <code>HttpClient</code>.
+ * @param request
+ * @return
+ */
+ protected HttpClientContext getHttpClientContext(PortletRequest request,
HttpRequestBase httpRequest)
+ {
+ return null;
+ }
+
protected HttpRequestBase createHttpRequest(CloseableHttpClient client,
String method, String uri, Map<String, String []> queryParams, Map<String,
String []> formPostParams, RenderRequest request) throws IOException,
URISyntaxException
{
// formMethod = FORM_MULTIPART_METHOD;
Modified:
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/proxy/SimpleReverseProxyPortlet.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/proxy/SimpleReverseProxyPortlet.java?rev=1631349&r1=1631348&r2=1631349&view=diff
==============================================================================
---
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/proxy/SimpleReverseProxyPortlet.java
(original)
+++
portals/applications/webcontent/trunk/portlets/src/main/java/org/apache/portals/applications/webcontent2/portlet/proxy/SimpleReverseProxyPortlet.java
Mon Oct 13 11:32:31 2014
@@ -63,7 +63,7 @@ public class SimpleReverseProxyPortlet e
{
super.init(portletConfig);
- proxyServiceCommand =
ProxyServices.createDefault().build(getProxyMappingRegistry(),
getHttpClientBuilder(), getHttpClientContextBuilder());
+ proxyServiceCommand = createProxyServiceCommand();
ProxyCommandUtils.initializeAllCommands(proxyServiceCommand);
DefaultReverseProxyService proxyService = new
DefaultReverseProxyService(proxyServiceCommand);
@@ -118,6 +118,10 @@ public class SimpleReverseProxyPortlet e
super.destroy();
}
+ protected ProxyProcessingChain createProxyServiceCommand() {
+ return ProxyServices.createDefault().build(getProxyMappingRegistry(),
getHttpClientBuilder(), getHttpClientContextBuilder());
+ }
+
protected ProxyMapping getProxyMapping()
{
PortletAnyProxyMapping portletAnyProxyMapping = new
PortletAnyProxyMapping(getPortletConfig());
Modified:
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/command/InitHttpRequestCommand.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/command/InitHttpRequestCommand.java?rev=1631349&r1=1631348&r2=1631349&view=diff
==============================================================================
---
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/command/InitHttpRequestCommand.java
(original)
+++
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/command/InitHttpRequestCommand.java
Mon Oct 13 11:32:31 2014
@@ -44,6 +44,13 @@ public class InitHttpRequestCommand exte
@Override
protected boolean executeInternal(final ProxyContext context) throws
ReverseProxyException, IOException
{
+ HttpRequestBase httpRequest = createHttpRequest(context);
+ context.setHttpRequest(httpRequest);
+ return false;
+ }
+
+ protected HttpRequestBase createHttpRequest(final ProxyContext context)
throws ReverseProxyException, IOException
+ {
HttpRequestBase httpRequest = null;
String method = context.getRequestContext().getMethod();
@@ -97,9 +104,6 @@ public class InitHttpRequestCommand exte
throw new
ReverseProxyException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Unsupported
method: " + method);
}
- context.setHttpRequest(httpRequest);
-
- return false;
+ return httpRequest;
}
-
}
Modified:
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/filter/SimpleReverseProxyFilter.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/filter/SimpleReverseProxyFilter.java?rev=1631349&r1=1631348&r2=1631349&view=diff
==============================================================================
---
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/filter/SimpleReverseProxyFilter.java
(original)
+++
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/filter/SimpleReverseProxyFilter.java
Mon Oct 13 11:32:31 2014
@@ -50,7 +50,7 @@ public class SimpleReverseProxyFilter ex
{
super.init(filterConfig);
- proxyServiceCommand =
ProxyServices.createDefault().build(getProxyMappingRegistry(),
getHttpClientBuilder(), getHttpClientContextBuilder());
+ proxyServiceCommand = createProxyServiceCommand();
ProxyCommandUtils.initializeAllCommands(proxyServiceCommand);
DefaultReverseProxyService proxyService = new
DefaultReverseProxyService(proxyServiceCommand);
@@ -106,4 +106,8 @@ public class SimpleReverseProxyFilter ex
ProxyCommandUtils.destroyAllCommands(proxyServiceCommand);
super.destroy();
}
+
+ protected ProxyProcessingChain createProxyServiceCommand() {
+ return ProxyServices.createDefault().build(getProxyMappingRegistry(),
getHttpClientBuilder(), getHttpClientContextBuilder());
+ }
}
Modified:
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChain.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChain.java?rev=1631349&r1=1631348&r2=1631349&view=diff
==============================================================================
---
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChain.java
(original)
+++
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChain.java
Mon Oct 13 11:32:31 2014
@@ -21,11 +21,16 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
+import org.apache.commons.chain.Chain;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ChainBase;
+import org.apache.commons.lang.ArrayUtils;
import org.apache.portals.applications.webcontent2.proxy.ReverseProxyException;
+/**
+ * <p>Convenience base class for reverse proxy processing {@link Chain}
implementation.</p>
+ */
public class ProxyProcessingChain extends ChainBase
{
@@ -49,6 +54,150 @@ public class ProxyProcessingChain extend
super(commands);
}
+ /**
+ * Return the size of command list.
+ * @return
+ */
+ public int getCommandCount() {
+ return commands.length;
+ }
+
+ /**
+ * Return the index of the given <code>command</code> in the command list.
+ * If not found, return -1.
+ * @param command
+ * @return
+ */
+ public int getCommandIndex(Command command) {
+ return ArrayUtils.indexOf(commands, command);
+ }
+
+ public int getCommandIndex(Class<? extends Command> commandType) {
+ if (commands == null) {
+ return -1;
+ }
+
+ if (commandType == null) {
+ return -1;
+ }
+
+ for (int i = 0; i < commands.length; i++) {
+ if (commands[i] != null &&
commandType.isAssignableFrom(commands[i].getClass())) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Find and return the command at the given <code>index</code> of the
command list.
+ * @param index
+ * @return
+ */
+ public Command getCommand(int index) {
+ if (index < 0 || index > commands.length) {
+ throw new IllegalArgumentException();
+ }
+
+ return commands[index];
+ }
+
+ /**
+ * <p>Add a {@link Command} at the given <code>index</code> of the list of
+ * {@link Command}s that will be called in turn when this {@link Chain}'s
+ * <code>execute()</code> method is called.
+ * Once <code>execute()</code> has been called
+ * at least once, it is no longer possible to add additional
+ * {@link Command}s; instead, an exception will be thrown.</p>
+
+ * @param index The index at which the command should be inserted
+ * @param command The {@link Command} to be added
+ * @exception IllegalArgumentException if <code>command</code>
+ * is <code>null</code>, or
+ * <code>index</code> is negative or greater than the size of the command
list.
+ * @exception IllegalStateException if this {@link Chain} has already
+ * been executed at least once, so no further configuration is allowed
+ */
+ public void addCommand(int index, Command command) {
+ if (index < 0 || index > commands.length) {
+ throw new IllegalArgumentException();
+ }
+
+ if (command == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (frozen) {
+ throw new IllegalStateException();
+ }
+
+ Command[] results = new Command[commands.length + 1];
+ System.arraycopy(commands, 0, results, 0, index);
+ System.arraycopy(commands, index, results, index + 1, commands.length
- index);
+ results[index] = command;
+ commands = results;
+ }
+
+ /**
+ * <p>Remove the {@link Command} at the given <code>index</code> of the
list of
+ * {@link Command}s that will be called in turn when this {@link Chain}'s
+ * <code>execute()</code> method is called.
+ * Once <code>execute()</code> has been called
+ * at least once, it is no longer possible to add additional
+ * {@link Command}s; instead, an exception will be thrown.</p>
+
+ * @param index The index to add in the command list
+ * @return true if successfully removed.
+ * @exception IllegalArgumentException if <code>index</code> is out of
bound.
+ * @exception IllegalStateException if this {@link Chain} has already
+ * been executed at least once, so no further configuration is allowed
+ */
+ public boolean removeCommand(int index) {
+ if (index < 0 || index > commands.length) {
+ throw new IllegalArgumentException();
+ }
+
+ if (frozen) {
+ throw new IllegalStateException();
+ }
+
+ Command[] results = new Command[commands.length - 1];
+ System.arraycopy(commands, 0, results, 0, index);
+ System.arraycopy(commands, index + 1, results, index, commands.length
- index - 1);
+ commands = results;
+
+ return true;
+ }
+
+ /**
+ * <p>Remove the {@link Command} from the list of
+ * {@link Command}s that will be called in turn when this {@link Chain}'s
+ * <code>execute()</code> method is called.
+ * Once <code>execute()</code> has been called
+ * at least once, it is no longer possible to add additional
+ * {@link Command}s; instead, an exception will be thrown.</p>
+
+ * @param command The {@link Command} to be added
+ * @return true if successfully removed.
+ * @exception IllegalArgumentException if <code>index</code> is out of
bound.
+ * @exception IllegalStateException if this {@link Chain} has already
+ * been executed at least once, so no further configuration is allowed
+ */
+ public boolean removeCommand(Command command) {
+ if (command == null) {
+ throw new IllegalArgumentException();
+ }
+
+ int index = getCommandIndex(command);
+
+ if (index != -1) {
+ return removeCommand(index);
+ }
+
+ return false;
+ }
+
@Override
public boolean execute(Context context) throws ReverseProxyException,
IOException
{
@@ -64,6 +213,11 @@ public class ProxyProcessingChain extend
return false;
}
+ /**
+ * Return a list including all the descendant commands, type of which is
<code>AbstractProxyCommand</code>
+ * in this proxy processing chain.
+ * @return
+ */
public List<AbstractProxyCommand> getAllProxyCommands()
{
List<AbstractProxyCommand> allCommands = new
LinkedList<AbstractProxyCommand>();
Modified:
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/servlet/SimpleReverseProxyServlet.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/servlet/SimpleReverseProxyServlet.java?rev=1631349&r1=1631348&r2=1631349&view=diff
==============================================================================
---
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/servlet/SimpleReverseProxyServlet.java
(original)
+++
portals/applications/webcontent/trunk/reverse-proxy/src/main/java/org/apache/portals/applications/webcontent2/proxy/servlet/SimpleReverseProxyServlet.java
Mon Oct 13 11:32:31 2014
@@ -55,7 +55,7 @@ public class SimpleReverseProxyServlet e
{
super.init(servletConfig);
- proxyServiceCommand =
ProxyServices.createDefault().build(getProxyMappingRegistry(),
getHttpClientBuilder(), getHttpClientContextBuilder());
+ proxyServiceCommand = createProxyServiceCommand();
ProxyCommandUtils.initializeAllCommands(proxyServiceCommand);
DefaultReverseProxyService proxyService = new
DefaultReverseProxyService(proxyServiceCommand);
@@ -111,4 +111,8 @@ public class SimpleReverseProxyServlet e
ProxyCommandUtils.destroyAllCommands(proxyServiceCommand);
super.destroy();
}
+
+ protected ProxyProcessingChain createProxyServiceCommand() {
+ return ProxyServices.createDefault().build(getProxyMappingRegistry(),
getHttpClientBuilder(), getHttpClientContextBuilder());
+ }
}
Added:
portals/applications/webcontent/trunk/reverse-proxy/src/test/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChainIndexingAdditionRemovalTest.java
URL:
http://svn.apache.org/viewvc/portals/applications/webcontent/trunk/reverse-proxy/src/test/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChainIndexingAdditionRemovalTest.java?rev=1631349&view=auto
==============================================================================
---
portals/applications/webcontent/trunk/reverse-proxy/src/test/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChainIndexingAdditionRemovalTest.java
(added)
+++
portals/applications/webcontent/trunk/reverse-proxy/src/test/java/org/apache/portals/applications/webcontent2/proxy/impl/ProxyProcessingChainIndexingAdditionRemovalTest.java
Mon Oct 13 11:32:31 2014
@@ -0,0 +1,99 @@
+/*
+ * 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.portals.applications.webcontent2.proxy.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.apache.commons.chain.Command;
+import
org.apache.portals.applications.webcontent2.proxy.command.CleanupCommand;
+import
org.apache.portals.applications.webcontent2.proxy.command.ExecuteHttpClientCommand;
+import
org.apache.portals.applications.webcontent2.proxy.command.InitializationCommand;
+import
org.apache.portals.applications.webcontent2.proxy.command.SerializeHttpEntityContentCommand;
+import org.junit.Test;
+
+public class ProxyProcessingChainIndexingAdditionRemovalTest
+{
+
+ @Test
+ public void testCommandIndexing() throws Exception {
+ Command [] commands = new Command [] {
+ new InitializationCommand(),
+ new ExecuteHttpClientCommand(),
+ new CleanupCommand()
+ };
+
+ ProxyProcessingChain chain = new ProxyProcessingChain();
+
+ for (Command command : commands) {
+ chain.addCommand(command);
+ }
+
+ assertEquals(3, chain.getCommandCount());
+
+ for (int i = 0; i < chain.getCommandCount(); i++) {
+ assertSame(commands[i], chain.getCommand(i));
+ assertSame(i, chain.getCommandIndex(commands[i]));
+ }
+
+ assertEquals(0, chain.getCommandIndex(Command.class));
+ assertEquals(0, chain.getCommandIndex(InitializationCommand.class));
+ assertEquals(1, chain.getCommandIndex(ExecuteHttpClientCommand.class));
+ assertEquals(2, chain.getCommandIndex(CleanupCommand.class));
+ assertEquals(-1,
chain.getCommandIndex(SerializeHttpEntityContentCommand.class));
+ }
+
+ @Test
+ public void testCommandAdditionRemoval() throws Exception {
+ ProxyProcessingChain chain = new ProxyProcessingChain();
+
+ assertEquals(0, chain.getCommandCount());
+
+ Command serializeHttpEntityContentCommand = new
SerializeHttpEntityContentCommand();
+ chain.addCommand(serializeHttpEntityContentCommand);
+ assertEquals(1, chain.getCommandCount());
+ assertSame(serializeHttpEntityContentCommand, chain.getCommand(0));
+ assertSame(0,
chain.getCommandIndex(serializeHttpEntityContentCommand));
+
+ Command initializationCommand = new InitializationCommand();
+ chain.addCommand(0, initializationCommand);
+ assertEquals(2, chain.getCommandCount());
+ assertSame(initializationCommand, chain.getCommand(0));
+ assertSame(0, chain.getCommandIndex(initializationCommand));
+
+ Command executeHttpClientCommand = new ExecuteHttpClientCommand();
+
chain.addCommand(chain.getCommandIndex(serializeHttpEntityContentCommand),
executeHttpClientCommand);
+ assertEquals(3, chain.getCommandCount());
+ assertSame(executeHttpClientCommand, chain.getCommand(1));
+ assertSame(1, chain.getCommandIndex(executeHttpClientCommand));
+
+ Command cleanupCommand = new CleanupCommand();
+ chain.addCommand(chain.getCommandCount(), cleanupCommand);
+ assertEquals(4, chain.getCommandCount());
+ assertSame(cleanupCommand, chain.getCommand(3));
+ assertSame(3, chain.getCommandIndex(cleanupCommand));
+
+ assertSame(initializationCommand, chain.getCommand(0));
+ assertSame(0, chain.getCommandIndex(initializationCommand));
+ assertSame(executeHttpClientCommand, chain.getCommand(1));
+ assertSame(1, chain.getCommandIndex(executeHttpClientCommand));
+ assertSame(serializeHttpEntityContentCommand, chain.getCommand(2));
+ assertSame(2,
chain.getCommandIndex(serializeHttpEntityContentCommand));
+ assertSame(cleanupCommand, chain.getCommand(3));
+ assertSame(3, chain.getCommandIndex(cleanupCommand));
+ }
+}