de.javakaffee.web.msm.integration.TestUtils.java Source code

Java tutorial

Introduction

Here is the source code for de.javakaffee.web.msm.integration.TestUtils.java

Source

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

import static de.javakaffee.web.msm.Configurations.NODE_AVAILABILITY_CACHE_TTL_KEY;
import static de.javakaffee.web.msm.integration.TestUtils.Predicates.elementAt;
import static de.javakaffee.web.msm.integration.TestUtils.Predicates.notNull;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Currency;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.LogManager;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;

import net.spy.memcached.MemcachedClient;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Valve;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Embedded;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.juli.logging.LogFactory;
import org.jboss.netty.buffer.ChannelBuffers;
import org.testng.Assert;
import org.testng.annotations.DataProvider;

import com.thimbleware.jmemcached.CacheElement;
import com.thimbleware.jmemcached.CacheImpl;
import com.thimbleware.jmemcached.Key;
import com.thimbleware.jmemcached.LocalCacheElement;
import com.thimbleware.jmemcached.MemCacheDaemon;
import com.thimbleware.jmemcached.storage.hash.ConcurrentLinkedHashMap;
import com.thimbleware.jmemcached.storage.hash.ConcurrentLinkedHashMap.EvictionPolicy;

import de.javakaffee.web.msm.MemcachedBackupSession;
import de.javakaffee.web.msm.MemcachedSessionService;
import de.javakaffee.web.msm.MemcachedSessionService.SessionManager;

/**
 * Integration test utils.
 *
 * @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
 */
public abstract class TestUtils {

    private static final String CONTEXT_PATH = "/";
    private static final String DEFAULT_HOST = "localhost";

    protected static final String PASSWORD = "secret";
    protected static final String USER_NAME = "testuser";
    protected static final String ROLE_NAME = "test";

    public static final String STICKYNESS_PROVIDER = "stickynessProvider";
    public static final String BOOLEAN_PROVIDER = "booleanProvider";

    static {
        initLogConfig(TestUtils.class);
        System.setProperty(NODE_AVAILABILITY_CACHE_TTL_KEY, "50");
    }

    private static void initLogConfig(final Class<? extends TestUtils> clazz) {
        final URL loggingProperties = clazz.getResource("/logging.properties");
        try {
            System.setProperty("java.util.logging.config.file",
                    new File(loggingProperties.toURI()).getAbsolutePath());
        } catch (final Exception e) {
            // we don't have a plain file (e.g. the case for msm-kryo-serializer etc), so we can skip reading the config
            return;
        }
        try {
            LogManager.getLogManager().readConfiguration();
        } catch (final Exception e) {
            LogFactory.getLog(TestUtils.class).error("Could not init logging configuration.", e);
        }
    }

    /**
     * Login using form based auth and return the session id.
     */
    public static String loginWithForm(final DefaultHttpClient client, final int tcPort)
            throws IOException, HttpException {
        final Response tc1Response1 = get(client, tcPort, null);
        final String sessionId = tc1Response1.getSessionId();
        assertNotNull(sessionId);
        assertTrue(tc1Response1.getContent().contains("j_security_check"));

        final Map<String, String> params = new HashMap<String, String>();
        params.put(LoginServlet.J_USERNAME, TestUtils.USER_NAME);
        params.put(LoginServlet.J_PASSWORD, TestUtils.PASSWORD);
        final Response tc1Response2 = post(client, tcPort, "/j_security_check", sessionId, params);
        assertEquals(tc1Response2.getSessionId(), sessionId);
        new RuntimeException("err").printStackTrace();
        assertEquals(tc1Response2.get(TestServlet.ID), sessionId);

        return tc1Response2.getSessionId();
    }

    public static String makeRequest(final HttpClient client, final int port, final String rsessionId)
            throws IOException, HttpException {
        // System.out.println( port + " >>>>>>>>>>>>>>>>>> Client Starting >>>>>>>>>>>>>>>>>>>>");
        String responseSessionId;
        final HttpGet method = new HttpGet("http://" + DEFAULT_HOST + ":" + port + CONTEXT_PATH);
        if (rsessionId != null) {
            method.setHeader("Cookie", "JSESSIONID=" + rsessionId);
        }

        // System.out.println( "cookies: " + method.getParams().getCookiePolicy() );
        //method.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
        final HttpResponse response = client.execute(method);

        if (response.getStatusLine().getStatusCode() != 200) {
            throw new RuntimeException("GET did not return status 200, but " + response.getStatusLine());
        }

        // System.out.println( ">>>>>>>>>>: " + method.getResponseBodyAsString() );
        responseSessionId = getSessionIdFromResponse(response);
        // System.out.println( "response cookie: " + responseSessionId );

        // We must consume the content so that the connection will be released
        response.getEntity().consumeContent();

        return responseSessionId == null ? rsessionId : responseSessionId;
    }

    public static Response get(final DefaultHttpClient client, final int port, final String rsessionId)
            throws IOException, HttpException {
        return get(client, port, null, rsessionId);
    }

    public static Response get(final DefaultHttpClient client, final int port, final String rsessionId,
            final Credentials credentials) throws IOException, HttpException {
        return get(client, port, null, rsessionId, null, null, credentials);
    }

    public static Response get(final DefaultHttpClient client, final int port, final String path,
            final String rsessionId) throws IOException, HttpException {
        return get(client, port, path, rsessionId, null, null, null);
    }

    public static Response get(final DefaultHttpClient client, final int port, final String path,
            final String rsessionId, final Map<String, String> params) throws IOException, HttpException {
        return get(client, port, path, rsessionId, null, params, null);
    }

    public static Response get(final DefaultHttpClient client, final int port, final String path,
            final String rsessionId, final SessionTrackingMode sessionTrackingMode,
            final Map<String, String> params, final Credentials credentials) throws IOException, HttpException {
        // System.out.println( port + " >>>>>>>>>>>>>>>>>> Client Starting >>>>>>>>>>>>>>>>>>>>");
        String url = getUrl(port, path);
        if (params != null && !params.isEmpty()) {
            url += toQueryString(params);
        }
        if (rsessionId != null && sessionTrackingMode == SessionTrackingMode.URL) {
            url += ";jsessionid=" + rsessionId;
        }
        final HttpGet method = new HttpGet(url);
        if (rsessionId != null && sessionTrackingMode == SessionTrackingMode.COOKIE) {
            method.setHeader("Cookie", "JSESSIONID=" + rsessionId);
        }

        final HttpResponse response = credentials == null ? client.execute(method)
                : executeRequestWithAuth(client, method, credentials);

        if (response.getStatusLine().getStatusCode() != 200) {
            throw new RuntimeException(
                    "GET " + path + " did not return status 200, but " + response.getStatusLine());
        }

        return readResponse(rsessionId, response);
    }

    private static String getUrl(final int port, String path) throws IllegalArgumentException {
        // we assume the context_path is "/"
        if (path != null && !path.startsWith("/")) {
            // but we can also fix this
            path = CONTEXT_PATH + path;
        }
        return "http://" + DEFAULT_HOST + ":" + port + (path != null ? path : CONTEXT_PATH);
    }

    /**
     * @param params
     * @return
     */
    private static String toQueryString(final Map<String, String> params) {
        final StringBuilder sb = new StringBuilder();
        sb.append("?");
        for (final Iterator<Entry<String, String>> iterator = params.entrySet().iterator(); iterator.hasNext();) {
            final Entry<String, String> entry = iterator.next();
            sb.append(entry.getKey()).append("=").append(entry.getValue());
            if (iterator.hasNext()) {
                sb.append("&");
            }
        }
        final String qs = sb.toString();
        return qs;
    }

    private static HttpResponse executeRequestWithAuth(final DefaultHttpClient client, final HttpUriRequest method,
            final Credentials credentials) throws IOException, ClientProtocolException {
        client.getCredentialsProvider().setCredentials(AuthScope.ANY, credentials);

        final BasicHttpContext localcontext = new BasicHttpContext();

        // Generate BASIC scheme object and stick it to the local
        // execution context
        final BasicScheme basicAuth = new BasicScheme();
        localcontext.setAttribute("preemptive-auth", basicAuth);

        // System.out.println( "cookies: " + method.getParams().getCookiePolicy() );
        //method.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
        return client.execute(method, localcontext);
    }

    private static Response readResponse(final String rsessionId, final HttpResponse response) throws IOException {
        final String responseSessionId = getSessionIdFromResponse(response);
        // System.out.println( "response cookie: " + responseSessionId );

        final StringBuilder sb = new StringBuilder();
        final Map<String, String> keyValues = new LinkedHashMap<String, String>();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                final String[] keyValue = line.split("=");
                if (keyValue.length > 0) {
                    keyValues.put(keyValue[0], keyValue.length > 1 ? keyValue[1] : null);
                }
            }
        } finally {
            if (reader != null) {
                reader.close();
            }
        }

        return new Response(response, responseSessionId == null ? rsessionId : responseSessionId, responseSessionId,
                sb.toString(), keyValues);
    }

    public static Response post(final DefaultHttpClient client, final int port, final String rsessionId,
            final String paramName, final String paramValue) throws IOException, HttpException {
        final Map<String, String> params = new HashMap<String, String>();
        params.put(paramName, paramValue);
        return post(client, port, null, rsessionId, params);
    }

    public static Response post(final DefaultHttpClient client, final int port, final String path,
            final String rsessionId, final Map<String, String> params) throws IOException, HttpException {
        return post(client, port, path, rsessionId, params, null, true);
    }

    public static Response post(final DefaultHttpClient client, final int port, final String path,
            final String rsessionId, final Map<String, String> params, @Nullable final Credentials credentials,
            final boolean followRedirects) throws IOException, HttpException {
        // System.out.println( port + " >>>>>>>>>>>>>>>>>> Client Starting >>>>>>>>>>>>>>>>>>>>");
        final String baseUri = "http://" + DEFAULT_HOST + ":" + port;
        final String url = getUrl(port, path);
        final HttpPost method = new HttpPost(url);
        if (rsessionId != null) {
            method.setHeader("Cookie", "JSESSIONID=" + rsessionId);
        }

        method.setEntity(createFormEntity(params));

        // System.out.println( "cookies: " + method.getParams().getCookiePolicy() );
        //method.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
        final HttpResponse response = credentials == null ? client.execute(method)
                : executeRequestWithAuth(client, method, credentials);

        final int statusCode = response.getStatusLine().getStatusCode();
        if (followRedirects && statusCode == 302) {
            return redirect(response, client, port, rsessionId, baseUri);
        }

        if (statusCode != 200 && !(!followRedirects && statusCode == 302)) {
            throw new RuntimeException(
                    "POST" + (path != null ? " " + path : "") + " did not return status 200, but "
                            + response.getStatusLine() + "\n" + toString(response.getEntity().getContent()));
        }

        return readResponse(rsessionId, response);
    }

    public static String toString(final InputStream in) {
        final StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(in));
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (final IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (final IOException e) {
                    /* ignore */}
            }
        }
        return sb.toString();
    }

    private static Response redirect(final HttpResponse response, final DefaultHttpClient client, final int port,
            final String rsessionId, final String baseUri) throws IOException, HttpException {
        final String location = response.getFirstHeader("Location").getValue();
        if (!location.startsWith(baseUri)) {
            throw new RuntimeException("There's s.th. wrong, the location header should start with the base URI "
                    + baseUri + ". The location header: " + location);
        }
        /* consume content so that the connection can be released
         */
        response.getEntity().consumeContent();

        /* redirect
         */
        final String redirectPath = location.substring(baseUri.length(), location.length());
        return get(client, port, redirectPath, rsessionId);
    }

    private static UrlEncodedFormEntity createFormEntity(final Map<String, String> params)
            throws UnsupportedEncodingException {
        final List<NameValuePair> parameters = new ArrayList<NameValuePair>();
        for (final Map.Entry<String, String> param : params.entrySet()) {
            parameters.add(new BasicNameValuePair(param.getKey(), param.getValue()));
        }
        final UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, HTTP.UTF_8);
        return entity;
    }

    public static String getSessionIdFromResponse(final HttpResponse response) {
        final Header cookie = response.getFirstHeader("Set-Cookie");
        if (cookie != null) {
            for (final HeaderElement header : cookie.getElements()) {
                if ("JSESSIONID".equals(header.getName())) {
                    return header.getValue();
                }
            }
        }
        return null;
    }

    public static StandardContext createContext() {
        final StandardEngine engine = new StandardEngine();
        engine.setService(new StandardService());

        final StandardContext context = new StandardContext();
        context.setPath("/");
        context.setSessionCookiePath("/");

        final WebappLoader webappLoader = new WebappLoader() {
            @Override
            public ClassLoader getClassLoader() {
                return Thread.currentThread().getContextClassLoader();
            }
        };
        context.setLoader(webappLoader);

        final StandardHost host = new StandardHost();
        engine.addChild(host);
        host.addChild(context);

        return context;
    }

    public static MemCacheDaemon<? extends CacheElement> createDaemon(final InetSocketAddress address)
            throws IOException {
        final MemCacheDaemon<LocalCacheElement> daemon = new MemCacheDaemon<LocalCacheElement>();
        final ConcurrentLinkedHashMap<Key, LocalCacheElement> cacheStorage = ConcurrentLinkedHashMap
                .create(EvictionPolicy.LRU, 100000, 1024 * 1024);
        daemon.setCache(new CacheImpl(cacheStorage));
        daemon.setAddr(address);
        daemon.setVerbose(true);
        return daemon;
    }

    public TomcatBuilder tomcatBuilder() {
        return new TomcatBuilder() {
            @Override
            @Nonnull
            protected SessionManager createSessionManager() {
                return TestUtils.this.createSessionManager();
            }
        };
    }

    /**
     * Must create a {@link SessionManager} for the current tomcat version.
     */
    @Nonnull
    protected abstract SessionManager createSessionManager();

    public static enum LoginType {
        BASIC, FORM
    }

    public static Context getContext(final Embedded tomcat) {
        return (Context) tomcat.getContainer().findChild(DEFAULT_HOST).findChild(CONTEXT_PATH);
    }

    public static SessionManager getManager(final Embedded tomcat) {
        return (SessionManager) getContext(tomcat).getManager();
    }

    public static MemcachedSessionService getService(final Embedded tomcat) {
        return ((SessionManager) getContext(tomcat).getManager()).getMemcachedSessionService();
    }

    public static Engine getEngine(final Embedded tomcat) {
        return (Engine) tomcat.getContainer();
    }

    public static void setChangeSessionIdOnAuth(final Embedded tomcat, final boolean changeSessionIdOnAuth) {
        final Engine engine = (StandardEngine) tomcat.getContainer();
        final Host host = (Host) engine.findChild(DEFAULT_HOST);
        final Container context = host.findChild(CONTEXT_PATH);
        final Valve first = context.getPipeline().getFirst();
        if (first instanceof AuthenticatorBase) {
            ((AuthenticatorBase) first).setChangeSessionIdOnAuthentication(changeSessionIdOnAuth);
        }
    }

    /**
     * A helper class for a response with a body containing key=value pairs
     * each in one line.
     */
    public static class Response {

        private final String _sessionId;
        private final String _responseSessionId;
        private final String _content;
        private final Map<String, String> _keyValues;
        private final HttpResponse _response;

        public Response(final HttpResponse response, final String sessionId, final String responseSessionId,
                final String content, final Map<String, String> keyValues) {
            _response = response;
            _sessionId = sessionId;
            _responseSessionId = responseSessionId;
            _content = content;
            _keyValues = keyValues;
        }

        public int getStatusCode() {
            return _response.getStatusLine().getStatusCode();
        }

        public String getHeader(final String name) {
            final Header header = _response.getFirstHeader(name);
            return header == null ? null : header.getValue();
        }

        public String getSessionId() {
            return _sessionId;
        }

        public String getResponseSessionId() {
            return _responseSessionId;
        }

        public String getContent() {
            return _content;
        }

        public Map<String, String> getKeyValues() {
            return _keyValues;
        }

        public String get(final String key) {
            return _keyValues.get(key);
        }

    }

    /**
     * Extracts the memcached node id from the provided session id.
     * @param sessionId the session id, that may contain the node id, e.g. as <code>${origsessionid}-${nodeid}</code>
     * @return the extracted node id or <code>null</code>, if no node information was found.
     */
    public static String extractNodeId(final String sessionId) {
        final int idx = sessionId.lastIndexOf('-');
        return idx > -1 ? sessionId.substring(idx + 1) : null;
    }

    public static void assertDeepEquals(final Object one, final Object another) {
        assertDeepEquals(one, another, new IdentityHashMap<Object, Object>());
    }

    public static void assertDeepEquals(final Object one, final Object another,
            final Map<Object, Object> alreadyChecked) {
        if (one == another) {
            return;
        }
        if (one == null && another != null || one != null && another == null) {
            Assert.fail("One of both is null: " + one + ", " + another);
        }
        if (alreadyChecked.containsKey(one)) {
            return;
        }
        alreadyChecked.put(one, another);

        Assert.assertEquals(one.getClass(), another.getClass());
        if (one.getClass().isPrimitive() || one instanceof String || one instanceof Character
                || one instanceof Boolean) {
            Assert.assertEquals(one, another);
            return;
        }

        if (Map.class.isAssignableFrom(one.getClass())) {
            final Map<?, ?> m1 = (Map<?, ?>) one;
            final Map<?, ?> m2 = (Map<?, ?>) another;
            Assert.assertEquals(m1.size(), m2.size());
            for (final Map.Entry<?, ?> entry : m1.entrySet()) {
                assertDeepEquals(entry.getValue(), m2.get(entry.getKey()));
            }
            return;
        }

        if (Set.class.isAssignableFrom(one.getClass())) {
            final Set<?> m1 = (Set<?>) one;
            final Set<?> m2 = (Set<?>) another;
            Assert.assertEquals(m1.size(), m2.size());
            final Iterator<?> iter1 = m1.iterator();
            final Iterator<?> iter2 = m2.iterator();
            while (iter1.hasNext()) {
                assertDeepEquals(iter1.next(), iter2.next());
            }
            return;
        }

        if (Number.class.isAssignableFrom(one.getClass())) {
            Assert.assertEquals(((Number) one).longValue(), ((Number) another).longValue());
            return;
        }

        if (one instanceof Currency) {
            // Check that the transient field defaultFractionDigits is initialized correctly (that was issue #34)
            final Currency currency1 = (Currency) one;
            final Currency currency2 = (Currency) another;
            Assert.assertEquals(currency1.getCurrencyCode(), currency2.getCurrencyCode());
            Assert.assertEquals(currency1.getDefaultFractionDigits(), currency2.getDefaultFractionDigits());
        }

        Class<? extends Object> clazz = one.getClass();
        while (clazz != null) {
            assertEqualDeclaredFields(clazz, one, another, alreadyChecked);
            clazz = clazz.getSuperclass();
        }

    }

    public static void assertEqualDeclaredFields(final Class<? extends Object> clazz, final Object one,
            final Object another, final Map<Object, Object> alreadyChecked) {
        for (final Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            if (!Modifier.isTransient(field.getModifiers())) {
                try {
                    assertDeepEquals(field.get(one), field.get(another), alreadyChecked);
                } catch (final IllegalArgumentException e) {
                    throw new RuntimeException(e);
                } catch (final IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * A simple serializable {@link HttpSessionActivationListener} that provides the
     * session id that was passed during {@link #sessionDidActivate(HttpSessionEvent)}
     * via {@link #getSessionDidActivate()}.
     */
    public static final class RecordingSessionActivationListener
            implements HttpSessionActivationListener, Serializable {

        private static final long serialVersionUID = 1L;

        private transient String _sessionDidActivate;

        @Override
        public void sessionWillPassivate(final HttpSessionEvent se) {
        }

        @Override
        public void sessionDidActivate(final HttpSessionEvent se) {
            _sessionDidActivate = se.getSession().getId();
        }

        /**
         * Returns the id of the session that was passed in {@link #sessionDidActivate(HttpSessionEvent)}.
         * @return a session id or <code>null</code>.
         */
        public String getSessionDidActivate() {
            return _sessionDidActivate;
        }

    }

    /**
     * Creates a map from the given keys and values (key1, value1, key2, value2, etc.).
     * @param <T> the type of the keys and values.
     * @param keysAndValues the keys and values, must be an even number of arguments.
     * @return a {@link Map} or null if no argument was given.
     */
    public static <T> Map<T, T> asMap(final T... keysAndValues) {
        if (keysAndValues == null) {
            return null;
        }
        if (keysAndValues.length % 2 != 0) {
            throw new IllegalArgumentException("You must provide an even number of arguments as key/value pairs.");
        }

        final Map<T, T> result = new HashMap<T, T>();
        for (int i = 0; i < keysAndValues.length; i++) {
            if ((i & 1) == 1) {
                result.put(keysAndValues[i - 1], keysAndValues[i]);
            }
        }

        return result;
    }

    public static enum SessionTrackingMode {
        COOKIE, URL
    }

    public static enum SessionAffinityMode {
        STICKY {
            @Override
            public boolean isSticky() {
                return true;
            }
        },
        NON_STICKY {
            @Override
            public boolean isSticky() {
                return false;
            }
        };

        public abstract boolean isSticky();
    }

    @DataProvider
    public static Object[][] stickynessProvider() {
        return new Object[][] { { SessionAffinityMode.STICKY }, { SessionAffinityMode.NON_STICKY } };
    }

    @DataProvider
    public static Object[][] booleanProvider() {
        return new Object[][] { { true }, { false } };
    }

    @Nonnull
    public static Key key(@Nonnull final String value) {
        return new Key(ChannelBuffers.wrappedBuffer(value.getBytes()));
    }

    @Nonnull
    public static MemcachedBackupSession createSession(@Nonnull final MemcachedSessionService service) {
        // return (MemcachedBackupSession) service.getManager().createSession( null );
        final MemcachedBackupSession session = service.createEmptySession();
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(23);
        session.setId("foo-n1");
        return session;
    }

    public static void waitForReconnect(final MemcachedClient client, final int expectedNumServers,
            final long timeToWait) throws InterruptedException, RuntimeException {
        final long start = System.currentTimeMillis();
        while (System.currentTimeMillis() < start + timeToWait) {
            if (client.getAvailableServers().size() >= expectedNumServers) {
                return;
            }
            Thread.sleep(20);
        }
        throw new RuntimeException("MemcachedClient did not reconnect after " + timeToWait + " millis.");
    }

    public static <T, V> T assertNotNullElementWaitingWithProxy(final int elementIndex, final long maxTimeToWait,
            final T objectToProxy) {
        return assertWaitingWithProxy(elementAt(elementIndex, notNull()), maxTimeToWait, objectToProxy);
    }

    @java.lang.SuppressWarnings("unchecked")
    public static <T, V> T assertWaitingWithProxy(final Predicate<V> predicate, final long maxTimeToWait,
            final T objectToProxy) {
        final Class<?>[] interfaces = objectToProxy.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces,
                new InvocationHandler() {
                    @Override
                    public Object invoke(final Object proxy, final Method method, final Object[] args)
                            throws Throwable {
                        return assertPredicateWaiting(predicate, maxTimeToWait, objectToProxy, method, args);
                    }
                });
    }

    private static <V> V assertPredicateWaiting(final Predicate<V> predicate, final long maxTimeToWait,
            final Object obj, final Method method, final Object[] args) throws Exception {
        final long start = System.currentTimeMillis();
        while (System.currentTimeMillis() < start + maxTimeToWait) {
            @java.lang.SuppressWarnings("unchecked")
            final V result = (V) method.invoke(obj, args);
            if (predicate.apply(result)) {
                return result;
            }
            try {
                Thread.sleep(10);
            } catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }
        throw new AssertionError("Expected not null, actual null.");
    }

    public static interface Predicate<T> {
        /**
         * Applies this predicate to the given object.
         *
         * @param input the input that the predicate should act on
         * @return the value of this predicate when applied to the input {@code t}
         */
        boolean apply(@Nullable T input);
    }

    public static class Predicates {

        private static final Predicate<Object> NOT_NULL = new Predicate<Object>() {
            @Override
            public boolean apply(final Object input) {
                return input != null;
            }
        };
        private static final Predicate<Object> IS_NULL = new Predicate<Object>() {
            @Override
            public boolean apply(final Object input) {
                return input == null;
            }
        };

        /**
         * Returns a predicate that evaluates to {@code true} if the object reference
         * being tested is not null.
         */
        @java.lang.SuppressWarnings("unchecked")
        public static <T> Predicate<T> notNull() {
            return (Predicate<T>) NOT_NULL;
        }

        /**
         * Returns a predicate that evaluates to {@code true} if the object reference
         * being tested is null.
         */
        @java.lang.SuppressWarnings("unchecked")
        public static <T> Predicate<T> isNull() {
            return (Predicate<T>) IS_NULL;
        }

        /**
         * Returns a predicate that evaluates to {@code true} if the object being
         * tested {@code equals()} the given target or both are null.
         */
        public static <T> Predicate<T> equalTo(@Nullable final T target) {
            return (target == null) ? Predicates.<T>isNull() : new Predicate<T>() {
                @Override
                public boolean apply(final T input) {
                    return target.equals(input);
                }
            };
        }

        public static <T> Predicate<T[]> elementAt(final int index, @Nonnull final Predicate<T> elementPredicate) {
            return new Predicate<T[]>() {
                @Override
                public boolean apply(final T[] input) {
                    return input != null && input.length > index && elementPredicate.apply(input[index]);
                }
            };
        }
    }

}