com.linkedin.restli.server.TestRestLiServer.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.restli.server.TestRestLiServer.java

Source

/*
   Copyright (c) 2012 LinkedIn Corp.
    
   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 com.linkedin.restli.server;

import com.linkedin.common.callback.Callback;
import com.linkedin.data.ByteString;
import com.linkedin.data.DataMap;
import com.linkedin.multipart.MultiPartMIMEReader;
import com.linkedin.multipart.MultiPartMIMEStreamRequestFactory;
import com.linkedin.multipart.MultiPartMIMEWriter;
import com.linkedin.multipart.utils.MIMETestUtils.MultiPartMIMEFullReaderCallback;
import com.linkedin.multipart.utils.MIMETestUtils.SinglePartMIMEFullReaderCallback;
import com.linkedin.parseq.Engine;
import com.linkedin.r2.message.Messages;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.r2.message.rest.RestException;
import com.linkedin.r2.message.rest.RestRequest;
import com.linkedin.r2.message.rest.RestRequestBuilder;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.message.rest.RestResponseBuilder;
import com.linkedin.r2.message.stream.StreamException;
import com.linkedin.r2.message.stream.StreamRequest;
import com.linkedin.r2.message.stream.StreamRequestBuilder;
import com.linkedin.r2.message.stream.StreamResponse;
import com.linkedin.r2.message.stream.entitystream.ByteStringWriter;
import com.linkedin.r2.message.stream.entitystream.EntityStreams;
import com.linkedin.r2.message.stream.entitystream.FullEntityReader;
import com.linkedin.restli.common.ErrorResponse;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.common.ProtocolVersion;
import com.linkedin.restli.common.RestConstants;
import com.linkedin.restli.common.attachments.RestLiAttachmentReader;
import com.linkedin.restli.internal.common.AllProtocolVersions;
import com.linkedin.restli.internal.common.AttachmentUtils;
import com.linkedin.restli.internal.common.TestConstants;
import com.linkedin.restli.internal.server.response.ErrorResponseBuilder;
import com.linkedin.restli.internal.server.util.DataMapUtils;
import com.linkedin.restli.internal.testutils.RestLiTestAttachmentDataSource;
import com.linkedin.restli.server.filter.Filter;
import com.linkedin.restli.server.filter.FilterRequestContext;
import com.linkedin.restli.server.filter.FilterResponseContext;
import com.linkedin.restli.server.resources.BaseResource;
import com.linkedin.restli.server.test.EasyMockResourceFactory;
import com.linkedin.restli.server.twitter.AsyncStatusCollectionResource;
import com.linkedin.restli.server.twitter.StatusCollectionResource;
import com.linkedin.restli.server.twitter.TwitterTestDataModels.Status;

import com.google.common.collect.ImmutableMap;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.mail.internet.ContentType;
import javax.mail.internet.ParseException;

import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import org.apache.commons.io.IOUtils;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.easymock.IAnswer;

import static org.easymock.EasyMock.eq;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

/**
 * "Integration" test that exercises a couple end-to-end use cases
 *
 * @author dellamag
 */
public class TestRestLiServer {
    private static final String DEBUG_HANDLER_RESPONSE_A = "Response A";
    private static final String DEBUG_HANDLER_RESPONSE_B = "Response B";

    private RestLiServer _server;
    private RestLiServer _serverWithFilters;
    private RestLiServer _serverWithCustomErrorResponseConfig; // configured different than server
    private EasyMockResourceFactory _resourceFactory;
    private Filter _mockFilter;

    @BeforeTest
    protected void setUp() {
        // silence null engine warning and get EasyMock failure if engine is used
        Engine fakeEngine = EasyMock.createMock(Engine.class);
        _mockFilter = EasyMock.createMock(Filter.class);
        setUpServer(fakeEngine);
        setupServerWithFilters(fakeEngine);
        setupServerWithCustomErrorResponseConfig(fakeEngine);
        EasyMock.replay(fakeEngine);
    }

    private void setupServerWithCustomErrorResponseConfig(Engine fakeEngine) {
        RestLiConfig customErrorResponseConfig = new RestLiConfig();
        customErrorResponseConfig.addResourcePackageNames("com.linkedin.restli.server.twitter");
        customErrorResponseConfig.setErrorResponseFormat(ErrorResponseFormat.MESSAGE_AND_DETAILS);
        customErrorResponseConfig.setInternalErrorMessage("kthxbye.");
        _serverWithCustomErrorResponseConfig = new RestLiServer(customErrorResponseConfig, _resourceFactory,
                fakeEngine);
    }

    private void setupServerWithFilters(Engine fakeEngine) {
        RestLiConfig config = new RestLiConfig(); // default is to use STRICT checking
        config.addResourcePackageNames("com.linkedin.restli.server.twitter");
        config.addFilter(_mockFilter);
        _serverWithFilters = new RestLiServer(config, _resourceFactory, fakeEngine);
    }

    private void setUpServer(Engine engine) {
        RestLiConfig config = new RestLiConfig();
        config.addResourcePackageNames("com.linkedin.restli.server.twitter");
        _resourceFactory = new EasyMockResourceFactory();

        RestLiDebugRequestHandler debugRequestHandlerA = new RestLiDebugRequestHandler() {
            @Override
            public void handleRequest(final RestRequest request, final RequestContext context,
                    final ResourceDebugRequestHandler resourceRequestHandler,
                    final RestLiAttachmentReader attachmentReader,
                    final RequestExecutionCallback<RestResponse> callback) {
                handleRequestWithCustomResponse(callback, DEBUG_HANDLER_RESPONSE_A);
            }

            @Override
            public String getHandlerId() {
                return "a";
            }
        };

        RestLiDebugRequestHandler debugRequestHandlerB = new RestLiDebugRequestHandler() {
            @Override
            @SuppressWarnings("unchecked")
            public void handleRequest(final RestRequest request, final RequestContext context,
                    final ResourceDebugRequestHandler resourceRequestHandler,
                    final RestLiAttachmentReader attachmentReader,
                    final RequestExecutionCallback<RestResponse> callback) {
                resourceRequestHandler.handleRequest(request, context,
                        EasyMock.createMock(RequestExecutionCallback.class));
                handleRequestWithCustomResponse(callback, DEBUG_HANDLER_RESPONSE_B);
            }

            @Override
            public String getHandlerId() {
                return "b";
            }
        };

        config.addDebugRequestHandlers(debugRequestHandlerA, debugRequestHandlerB);
        _server = new RestLiServer(config, _resourceFactory, engine);
    }

    private void handleRequestWithCustomResponse(final RequestExecutionCallback<RestResponse> callback,
            final String response) {
        RestResponseBuilder responseBuilder = new RestResponseBuilder();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        try {
            IOUtils.write(response, outputStream);
        } catch (IOException exc) {
            //Test will fail later.
        }

        responseBuilder.setEntity(outputStream.toByteArray());
        callback.onSuccess(responseBuilder.build(), null, null);
    }

    private enum RestOrStream {
        REST, STREAM
    }

    @AfterTest
    protected void tearDown() {
        _resourceFactory = null;
        _server = null;
        EasyMock.reset(_mockFilter, _mockFilter);
    }

    @AfterMethod
    protected void afterMethod() {
        EasyMock.reset(_mockFilter, _mockFilter);
    }

    @DataProvider(name = "validClientProtocolVersionData")
    public Object[][] provideValidClientProtocolVersionData() {
        return new Object[][] {
                //Rest
                { _server, AllProtocolVersions.BASELINE_PROTOCOL_VERSION,
                        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.REST },
                { _server, AllProtocolVersions.LATEST_PROTOCOL_VERSION,
                        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.REST },
                { _server, AllProtocolVersions.NEXT_PROTOCOL_VERSION, RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                        RestOrStream.REST },
                { _server, AllProtocolVersions.PREVIOUS_PROTOCOL_VERSION,
                        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.REST },
                //Stream
                { _server, AllProtocolVersions.BASELINE_PROTOCOL_VERSION,
                        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.STREAM },
                { _server, AllProtocolVersions.LATEST_PROTOCOL_VERSION,
                        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.STREAM },
                { _server, AllProtocolVersions.NEXT_PROTOCOL_VERSION, RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                        RestOrStream.STREAM },
                { _server, AllProtocolVersions.PREVIOUS_PROTOCOL_VERSION,
                        RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.STREAM } };
    }

    @DataProvider(name = "invalidClientProtocolVersionData")
    public Object[][] provideInvalidClientProtocolVersionData() {
        ProtocolVersion greaterThanNext = new ProtocolVersion(
                AllProtocolVersions.NEXT_PROTOCOL_VERSION.getMajor() + 1, 0, 0);

        return new Object[][] {
                //Rest
                { _server, greaterThanNext, RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.REST },
                { _server, new ProtocolVersion(0, 0, 0), RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                        RestOrStream.REST },

                //Stream
                { _server, greaterThanNext, RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, RestOrStream.STREAM },
                { _server, new ProtocolVersion(0, 0, 0), RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                        RestOrStream.STREAM } };
    }

    @DataProvider(name = "restOrStream")
    public Object[][] restOrStream() {
        return new Object[][] { { RestOrStream.REST }, { RestOrStream.STREAM } };
    }

    @Test(dataProvider = "restOrStream")
    public void testServer(final RestOrStream restOrStream) throws Exception {
        testValidRequest(_server, null, false, RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, restOrStream);
    }

    @Test(dataProvider = "restOrStream")
    public void testServerWithFilters(final RestOrStream restOrStream) throws Exception {
        testValidRequest(_serverWithFilters, null, true, RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                restOrStream);
    }

    @Test(dataProvider = "validClientProtocolVersionData")
    public void testValidClientProtocolVersion(RestLiServer server, ProtocolVersion clientProtocolVersion,
            String headerConstant, RestOrStream restOrStream) throws URISyntaxException {
        testValidRequest(server, clientProtocolVersion, false, headerConstant, restOrStream);
    }

    private void testValidRequest(RestLiServer restLiServer, final ProtocolVersion clientProtocolVersion,
            boolean filters, final String headerConstant, final RestOrStream restOrStream)
            throws URISyntaxException {
        RestRequest request = null;
        StreamRequest streamRequest = null;
        if (clientProtocolVersion != null) {
            if (restOrStream == RestOrStream.REST) {
                request = new RestRequestBuilder(new URI("/statuses/1"))
                        .setHeader(headerConstant, clientProtocolVersion.toString()).build();
            } else {
                streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                        .setHeader(headerConstant, clientProtocolVersion.toString())
                        .build(EntityStreams.emptyStream());
            }
        } else {
            if (restOrStream == RestOrStream.REST) {
                request = new RestRequestBuilder(new URI("/statuses/1")).build();
            } else {
                streamRequest = new StreamRequestBuilder(new URI("/statuses/1")).build(EntityStreams.emptyStream());
            }
        }

        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L))).andReturn(buildStatusRecord()).once();
        if (filters) {
            _mockFilter.onRequest(EasyMock.anyObject(FilterRequestContext.class));
            EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
                @Override
                public Object answer() throws Throwable {
                    return CompletableFuture.completedFuture(null);
                }
            }).times(1);

            _mockFilter.onResponse(EasyMock.anyObject(FilterRequestContext.class),
                    EasyMock.anyObject(FilterResponseContext.class));
            EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
                @Override
                public Object answer() throws Throwable {
                    return CompletableFuture.completedFuture(null);
                }
            }).times(1);
            EasyMock.replay(_mockFilter);
        }
        EasyMock.replay(statusResource);

        final Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                assertEquals(restResponse.getStatus(), 200);
                assertTrue(restResponse.getEntity().length() > 0);
                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);

                if (clientProtocolVersion != null) {
                    assertEquals(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, headerConstant,
                            "Rest.li protocol header name is unexpected.");
                }
            }

            @Override
            public void onError(Throwable e) {
                fail();
            }
        };

        if (restOrStream == RestOrStream.REST) {
            restLiServer.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            Callback<StreamResponse> streamResponseCallback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    Messages.toRestResponse(streamResponse, new Callback<RestResponse>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestResponse result) {
                            restResponseCallback.onSuccess(result);
                        }
                    });
                }

                @Override
                public void onError(Throwable e) {
                    fail();
                }
            };

            restLiServer.handleRequest(streamRequest, new RequestContext(), streamResponseCallback);
        }
        if (filters) {
            EasyMock.verify(_mockFilter, _mockFilter);
        }
    }

    @Test(dataProvider = "invalidClientProtocolVersionData")
    public void testInvalidClientProtocolVersion(RestLiServer server, ProtocolVersion clientProtocolVersion,
            String headerConstant, RestOrStream restOrStream) throws URISyntaxException {
        testBadRequest(server, clientProtocolVersion, headerConstant, restOrStream);
    }

    private void testBadRequest(RestLiServer restLiServer, final ProtocolVersion clientProtocolVersion,
            String headerConstant, final RestOrStream restOrStream) throws URISyntaxException {
        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail("The request should have failed!");
            }

            @Override
            public void onError(Throwable e) {
                assertEquals(((RestException) e).getResponse().getStatus(), 400);
                String expectedErrorMessage = "Rest.li protocol version " + clientProtocolVersion
                        + " used by the client is not supported!";
                assertEquals(e.getCause().getMessage(), expectedErrorMessage);
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1"))
                    .setHeader(headerConstant, clientProtocolVersion.toString()).build();

            restLiServer.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                    .setHeader(headerConstant, clientProtocolVersion.toString()).build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail("The request should have failed!");
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };
            restLiServer.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @SuppressWarnings({ "unchecked" })
    @Test(dataProvider = "restOrStream")
    public void testAsyncServer(final RestOrStream restOrStream) throws Exception {
        final AsyncStatusCollectionResource statusResource = getMockResource(AsyncStatusCollectionResource.class);

        statusResource.get(eq(1L), EasyMock.<Callback<Status>>anyObject());
        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
            @Override
            public Object answer() throws Throwable {
                Callback<Status> callback = (Callback<Status>) EasyMock.getCurrentArguments()[1];
                Status stat = buildStatusRecord();
                callback.onSuccess(stat);
                return null;
            }
        });
        EasyMock.replay(statusResource);

        final Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                assertEquals(restResponse.getStatus(), 200);
                assertTrue(restResponse.getEntity().length() > 0);
                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);
            }

            @Override
            public void onError(Throwable e) {
                fail();
            }
        };

        if (restOrStream == RestOrStream.REST) {
            final RestRequest request = new RestRequestBuilder(new URI("/asyncstatuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build();

            _server.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            final StreamRequest streamRequest = new StreamRequestBuilder(new URI("/asyncstatuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build(EntityStreams.emptyStream());

            final Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    Messages.toRestResponse(streamResponse, new Callback<RestResponse>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestResponse result) {
                            restResponseCallback.onSuccess(result);
                        }
                    });
                }

                @Override
                public void onError(Throwable e) {
                    fail();
                }
            };

            _server.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = "restOrStream")
    public void testSyncNullObject404(final RestOrStream restOrStream) throws Exception {
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L))).andReturn(null).once();
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail("We should not get a success here. The server should have returned a 404!");
            }

            @Override
            public void onError(Throwable e) {
                RestException restException = (RestException) e;
                assertEquals(restException.getResponse().getStatus(), 404, "We should get a 404 back here!");
                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build();

            _server.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail("We should not get a success here. The server should have returned a 404!");
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _server.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = TestConstants.RESTLI_PROTOCOL_1_2_PREFIX + "protocolVersions")
    public void testPreprocessingError(final ProtocolVersion protocolVersion, final String errorResponseHeaderName,
            final RestOrStream restOrStream) throws Exception {
        //Bad key type will generate a routing error
        final StatusCollectionResource statusResource = _resourceFactory.getMock(StatusCollectionResource.class);
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                assertEquals(restResponse.getStatus(), 400);
                assertTrue(restResponse.getEntity().length() > 0);
                assertEquals(restResponse.getHeader(errorResponseHeaderName), RestConstants.HEADER_VALUE_ERROR);

                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/abcd"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString()).build();

            _server.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/abcd"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail();
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _server.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = TestConstants.RESTLI_PROTOCOL_1_2_PREFIX + "protocolVersions")
    public void testApplicationException(final ProtocolVersion protocolVersion,
            final String errorResponseHeaderName, final RestOrStream restOrStream) throws Exception {
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L)))
                .andThrow(new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR, "Mock Exception"))
                .once();
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                try {
                    assertEquals(restResponse.getStatus(), 500);
                    assertTrue(restResponse.getEntity().length() > 0);
                    assertEquals(restResponse.getHeader(errorResponseHeaderName), RestConstants.HEADER_VALUE_ERROR);
                    ErrorResponse responseBody = DataMapUtils.read(restResponse.getEntity().asInputStream(),
                            ErrorResponse.class);
                    assertEquals(responseBody.getMessage(), "Mock Exception");
                    assertEquals(responseBody.getExceptionClass(),
                            "com.linkedin.restli.server.RestLiServiceException");
                    assertTrue(responseBody.getStackTrace().startsWith(
                            "com.linkedin.restli.server.RestLiServiceException [HTTP Status:500]: Mock Exception"));
                    assertEquals(responseBody.getStatus().intValue(), 500);

                    EasyMock.verify(statusResource);
                    EasyMock.reset(statusResource);
                } catch (Exception e2) {
                    fail(e2.toString());
                }
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString()).build();

            _server.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail();
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _server.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = "restOrStream")
    public void testInternalErrorMessage(final RestOrStream restOrStream) throws Exception {
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L))).andThrow(new IllegalArgumentException("oops")).once();
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                try {
                    ErrorResponse responseBody = DataMapUtils.read(restResponse.getEntity().asInputStream(),
                            ErrorResponse.class);
                    assertEquals(responseBody.getMessage(), ErrorResponseBuilder.DEFAULT_INTERNAL_ERROR_MESSAGE);

                    EasyMock.verify(statusResource);
                    EasyMock.reset(statusResource);
                } catch (Exception e2) {
                    fail(e2.toString());
                }
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build();

            _server.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail();
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _server.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = "restOrStream")
    public void testCustomizedInternalErrorMessage(final RestOrStream restOrStream) throws Exception {
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L))).andThrow(new IllegalArgumentException("oops")).once();
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                try {
                    ErrorResponse responseBody = DataMapUtils.read(restResponse.getEntity().asInputStream(),
                            ErrorResponse.class);
                    assertEquals(responseBody.getMessage(), "kthxbye.");

                    EasyMock.verify(statusResource);
                    EasyMock.reset(statusResource);
                } catch (Exception e2) {
                    fail(e2.toString());
                }
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build();

            _serverWithCustomErrorResponseConfig.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION,
                            AllProtocolVersions.BASELINE_PROTOCOL_VERSION.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail();
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _serverWithCustomErrorResponseConfig.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = TestConstants.RESTLI_PROTOCOL_1_2_PREFIX + "protocolVersions")
    public void testMessageAndDetailsErrorFormat(final ProtocolVersion protocolVersion,
            final String errorResponseHeaderName, final RestOrStream restOrStream) throws Exception {
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        final DataMap details = new DataMap();
        details.put("errorKey", "errorDetail");
        EasyMock.expect(statusResource.get(eq(1L)))
                .andThrow(new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR, "Mock Exception")
                        .setErrorDetails(details))
                .once();
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                try {
                    assertEquals(restResponse.getStatus(), 500);
                    assertTrue(restResponse.getEntity().length() > 0);
                    assertEquals(restResponse.getHeader(errorResponseHeaderName), RestConstants.HEADER_VALUE_ERROR);
                    ErrorResponse responseBody = DataMapUtils.read(restResponse.getEntity().asInputStream(),
                            ErrorResponse.class);

                    // in this test, we're using the _serverWithCustomErrorResponseConfig (see below), which has been configure to use the
                    // MESSAGE_AND_DETAILS ErrorResponseFormat, so stack trace and other error response parts should be absent
                    assertEquals(responseBody.getMessage(), "Mock Exception");
                    assertEquals(responseBody.getErrorDetails().data().getString("errorKey"), "errorDetail");
                    assertFalse(responseBody.hasExceptionClass());
                    assertFalse(responseBody.hasStackTrace());
                    assertFalse(responseBody.hasStatus());

                    EasyMock.verify(statusResource);
                    EasyMock.reset(statusResource);
                } catch (Exception e2) {
                    fail(e2.toString());
                }
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString()).build();

            _serverWithCustomErrorResponseConfig.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail();
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _serverWithCustomErrorResponseConfig.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test(dataProvider = TestConstants.RESTLI_PROTOCOL_1_2_PREFIX + "protocolVersions")
    public void testPostProcessingException(final ProtocolVersion protocolVersion,
            final String errorResponseHeaderName, final RestOrStream restOrStream) throws Exception {
        //request for nested projection within string field will generate error
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L))).andReturn(buildStatusRecord()).once();
        EasyMock.replay(statusResource);

        Callback<RestResponse> restResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                try {
                    assertEquals(restResponse.getStatus(), 500);
                    assertTrue(restResponse.getEntity().length() > 0);
                    assertEquals(restResponse.getHeader(errorResponseHeaderName), RestConstants.HEADER_VALUE_ERROR);

                    EasyMock.verify(statusResource);
                    EasyMock.reset(statusResource);
                } catch (Exception e2) {
                    fail(e2.toString());
                }
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1?fields=text:(invalid)"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString()).build();

            _server.handleRequest(request, new RequestContext(), restResponseCallback);
        } else {
            StreamRequest streamRequest = new StreamRequestBuilder(new URI("/statuses/1?fields=text:(invalid)"))
                    .setHeader(RestConstants.HEADER_RESTLI_PROTOCOL_VERSION, protocolVersion.toString())
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    fail();
                }

                @Override
                public void onError(Throwable e) {
                    Messages.toRestException((StreamException) e, new Callback<RestException>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestException result) {
                            restResponseCallback.onError(result);
                        }
                    });
                }
            };

            _server.handleRequest(streamRequest, new RequestContext(), callback);
        }
    }

    @Test
    public void testRestLiConfig() {
        // #1 test that setters replace entries set
        RestLiConfig config = new RestLiConfig();
        config.setResourcePackageNames("foo,bar,baz");
        assertEquals(3, config.getResourcePackageNamesSet().size());
        config.setResourcePackageNames("foo");
        assertEquals(1, config.getResourcePackageNamesSet().size());
        config.setResourcePackageNames("foo,bar,baz");
        assertEquals(3, config.getResourcePackageNamesSet().size());

        Set<String> packageSet = new HashSet<String>();
        packageSet.add("a");
        packageSet.add("b");
        config.setResourcePackageNamesSet(packageSet);
        assertEquals(2, config.getResourcePackageNamesSet().size());

        // #2 'add' method doesn't replace set, of course
        config.addResourcePackageNames("c", "d");
        assertEquals(4, config.getResourcePackageNamesSet().size());

        // #3 test that 'empty' values are ignored
        config.setResourcePackageNames("");
        assertEquals(4, config.getResourcePackageNamesSet().size());
        config.setResourcePackageNames("   ");
        assertEquals(4, config.getResourcePackageNamesSet().size());
        config.setResourcePackageNames(null);
        assertEquals(4, config.getResourcePackageNamesSet().size());
        config.setResourcePackageNamesSet(Collections.<String>emptySet());
        assertEquals(4, config.getResourcePackageNamesSet().size());
        config.setResourcePackageNamesSet(null);
        assertEquals(4, config.getResourcePackageNamesSet().size());
    }

    @Test(dataProvider = "restOrStream")
    public void testDebugRequestHandlers(final RestOrStream restOrStream) throws URISyntaxException {
        //Without a resource
        final Callback<RestResponse> noResourceRestResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                assertEquals(restResponse.getStatus(), 200);
                String responseString = restResponse.getEntity().asString(Charset.defaultCharset());
                Assert.assertEquals(responseString, DEBUG_HANDLER_RESPONSE_A);
            }

            @Override
            public void onError(Throwable e) {
                fail();
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1/__debug/a/s")).build();

            _server.handleRequest(request, new RequestContext(), noResourceRestResponseCallback);
        } else {
            StreamRequest request = new StreamRequestBuilder(new URI("/statuses/1/__debug/a/s"))
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    Messages.toRestResponse(streamResponse, new Callback<RestResponse>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestResponse result) {
                            noResourceRestResponseCallback.onSuccess(result);
                        }
                    });
                }

                @Override
                public void onError(Throwable e) {
                    fail();
                }
            };

            _server.handleRequest(request, new RequestContext(), callback);
        }

        //With a resource this time
        final StatusCollectionResource statusResource = getMockResource(StatusCollectionResource.class);
        EasyMock.expect(statusResource.get(eq(1L))).andReturn(buildStatusRecord()).once();
        EasyMock.replay(statusResource);

        final Callback<RestResponse> resourceRestResponseCallback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                assertEquals(restResponse.getStatus(), 200);
                String responseString = restResponse.getEntity().asString(Charset.defaultCharset());
                Assert.assertEquals(responseString, DEBUG_HANDLER_RESPONSE_B);

                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);
            }

            @Override
            public void onError(Throwable e) {
                fail();
            }
        };

        if (restOrStream == RestOrStream.REST) {
            RestRequest request = new RestRequestBuilder(new URI("/statuses/1/__debug/b")).build();

            _server.handleRequest(request, new RequestContext(), resourceRestResponseCallback);
        } else {
            StreamRequest request = new StreamRequestBuilder(new URI("/statuses/1/__debug/b"))
                    .build(EntityStreams.emptyStream());

            Callback<StreamResponse> callback = new Callback<StreamResponse>() {
                @Override
                public void onSuccess(StreamResponse streamResponse) {
                    Messages.toRestResponse(streamResponse, new Callback<RestResponse>() {
                        @Override
                        public void onError(Throwable e) {
                            Assert.fail();
                        }

                        @Override
                        public void onSuccess(RestResponse result) {
                            resourceRestResponseCallback.onSuccess(result);
                        }
                    });
                }

                @Override
                public void onError(Throwable e) {
                    fail();
                }
            };

            _server.handleRequest(request, new RequestContext(), callback);
        }
    }

    @DataProvider(name = TestConstants.RESTLI_PROTOCOL_1_2_PREFIX + "protocolVersions")
    private Object[][] protocolVersions1And2DataProvider() {
        return new Object[][] {
                //Rest
                { AllProtocolVersions.RESTLI_PROTOCOL_1_0_0.getProtocolVersion(),
                        RestConstants.HEADER_LINKEDIN_ERROR_RESPONSE, RestOrStream.REST },
                { AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion(),
                        RestConstants.HEADER_RESTLI_ERROR_RESPONSE, RestOrStream.REST },

                //Stream
                { AllProtocolVersions.RESTLI_PROTOCOL_1_0_0.getProtocolVersion(),
                        RestConstants.HEADER_LINKEDIN_ERROR_RESPONSE, RestOrStream.STREAM },
                { AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion(),
                        RestConstants.HEADER_RESTLI_ERROR_RESPONSE, RestOrStream.STREAM } };
    }

    @Test
    public void testRestRequestAttachmentsPresent() throws Exception {
        //This test verifies that a RestRequest sent to the RestLiServer throws an exception if the content type is multipart/related
        RestRequest contentTypeMultiPartRelated = new RestRequestBuilder(new URI("/statuses/abcd"))
                .setHeader(RestConstants.HEADER_CONTENT_TYPE, RestConstants.HEADER_VALUE_MULTIPART_RELATED).build();

        Callback<RestResponse> callback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                assertEquals(restResponse.getStatus(), 415);
                assertTrue(restResponse.getEntity().length() > 0);
                assertEquals(restResponse.getEntity().asString(Charset.defaultCharset()),
                        "This server cannot handle requests with a content type of multipart/related");
            }
        };

        _server.handleRequest(contentTypeMultiPartRelated, new RequestContext(), callback);
    }

    @Test
    public void testRestRequestResponseAttachmentsDesired() throws Exception {
        //This test verifies that a RestRequest sent to the RestLiServer throws an exception if the accept type
        //includes multipart related
        RestRequest acceptTypeMultiPartRelated = new RestRequestBuilder(new URI("/statuses/abcd"))
                .setHeader(RestConstants.HEADER_ACCEPT, RestConstants.HEADER_VALUE_MULTIPART_RELATED).build();

        Callback<RestResponse> callback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                assertEquals(restResponse.getStatus(), 406);
                assertTrue(restResponse.getEntity().length() > 0);
                assertEquals(restResponse.getEntity().asString(Charset.defaultCharset()),
                        "This server cannot handle requests with an accept type of multipart/related");
            }
        };

        _server.handleRequest(acceptTypeMultiPartRelated, new RequestContext(), callback);
    }

    @Test
    public void testRestRequestAttemptVerifyParseFailed() throws Exception {
        //This test verifies that a RestRequest sent to the RestLiServer throws an exception if the content type or accept types
        //fail to parse properly. This occurs when we try to verify that the request's content type or accept types do
        //not include multipart/related.
        RestRequest invalidContentTypeRequest = new RestRequestBuilder(new URI("/statuses/abcd"))
                .setHeader(RestConstants.HEADER_CONTENT_TYPE, "").build();

        Callback<RestResponse> callback = new Callback<RestResponse>() {
            @Override
            public void onSuccess(RestResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof RestException);
                RestException restException = (RestException) e;
                RestResponse restResponse = restException.getResponse();

                assertEquals(restResponse.getStatus(), 400);
                assertTrue(restResponse.getEntity().length() > 0);
                assertEquals(restResponse.getEntity().asString(Charset.defaultCharset()),
                        "Unable to parse content or accept types.");
            }
        };

        _server.handleRequest(invalidContentTypeRequest, new RequestContext(), callback);
    }

    @Test
    public void testStreamRequestMultiplexedRequestMultiPartAcceptType() throws Exception {
        //This test verifies that a StreamRequest sent to the RestLiServer throws an exception if the accept type contains
        //multipart/related.
        StreamRequest streamRequestMux = new StreamRequestBuilder(new URI("/mux"))
                .setHeader(RestConstants.HEADER_ACCEPT, RestConstants.HEADER_VALUE_MULTIPART_RELATED)
                .build(EntityStreams.emptyStream());

        Callback<StreamResponse> callback = new Callback<StreamResponse>() {
            @Override
            public void onSuccess(StreamResponse restResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                assertTrue(e instanceof StreamException);
                StreamException streamException = (StreamException) e;
                StreamResponse streamResponse = streamException.getResponse();

                assertEquals(streamResponse.getStatus(), 406);
                final FullEntityReader fullEntityReader = new FullEntityReader(new Callback<ByteString>() {
                    @Override
                    public void onError(Throwable e) {
                        Assert.fail();
                    }

                    @Override
                    public void onSuccess(ByteString result) {
                        //We have the body so assert
                        assertTrue(result.length() > 0);
                        assertEquals(result.asString(Charset.defaultCharset()),
                                "This server cannot handle multiplexed requests that have an accept type of multipart/related");
                    }
                });
                streamResponse.getEntityStream().setReader(fullEntityReader);
            }
        };

        _server.handleRequest(streamRequestMux, new RequestContext(), callback);
    }

    @Test
    public void testRequestAttachmentsAndResponseAttachments() throws Exception {
        //This test verifies the server's ability to accept request attachments and send back response attachments. This is the
        //main test to verify the wire protocol for streaming. We send a payload that contains the rest.li payload and some attachments
        //and we send a response back with a rest.li payload and some attachments.

        //Define the server side resource attachments to be sent back.
        final RestLiResponseAttachments.Builder responseAttachmentsBuilder = new RestLiResponseAttachments.Builder();
        responseAttachmentsBuilder.appendSingleAttachment(
                new RestLiTestAttachmentDataSource("1", ByteString.copyString("one", Charset.defaultCharset())));

        Capture<ResourceContext> resourceContextCapture = new Capture<ResourceContext>();
        final AsyncStatusCollectionResource statusResource = getMockResource(AsyncStatusCollectionResource.class,
                EasyMock.capture(resourceContextCapture));

        statusResource.streamingAction(EasyMock.<String>anyObject(), EasyMock.<RestLiAttachmentReader>anyObject(),
                EasyMock.<Callback<Long>>anyObject());
        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
            @Override
            public Object answer() throws Throwable {
                //Verify there are still attachments to be read.
                final RestLiAttachmentReader attachmentReader = (RestLiAttachmentReader) EasyMock
                        .getCurrentArguments()[1];
                Assert.assertFalse(attachmentReader.haveAllAttachmentsFinished());

                //Verify the action param.
                Assert.assertEquals((String) EasyMock.getCurrentArguments()[0], "someMetadata");

                //Set the response attachments
                resourceContextCapture.getValue().setResponseAttachments(responseAttachmentsBuilder.build());

                //Now respond back to the request.
                @SuppressWarnings("unchecked")
                Callback<Long> callback = (Callback<Long>) EasyMock.getCurrentArguments()[2];
                callback.onSuccess(1234l);
                return null;
            }
        });
        EasyMock.replay(statusResource);

        //Now we create a multipart/related payload.
        final String payload = "{\"metadata\": \"someMetadata\"}";
        final ByteStringWriter byteStringWriter = new ByteStringWriter(
                ByteString.copyString(payload, Charset.defaultCharset()));
        final MultiPartMIMEWriter.Builder builder = new MultiPartMIMEWriter.Builder();
        AttachmentUtils.appendSingleAttachmentToBuilder(builder,
                new RestLiTestAttachmentDataSource("2", ByteString.copyString("two", Charset.defaultCharset())));
        final MultiPartMIMEWriter writer = AttachmentUtils.createMultiPartMIMEWriter(byteStringWriter,
                "application/json", builder);

        final StreamRequest streamRequest = MultiPartMIMEStreamRequestFactory.generateMultiPartMIMEStreamRequest(
                new URI("/asyncstatuses/?action=streamingAction"), "related", writer,
                Collections.<String, String>emptyMap(), "POST",
                ImmutableMap.of(RestConstants.HEADER_ACCEPT, RestConstants.HEADER_VALUE_MULTIPART_RELATED),
                Collections.emptyList());

        final Callback<StreamResponse> callback = new Callback<StreamResponse>() {
            @Override
            public void onSuccess(StreamResponse streamResponse) {
                //Before reading the data make sure top level type is multipart/related
                Assert.assertEquals(streamResponse.getStatus(), 200);
                try {
                    ContentType contentType = new ContentType(
                            streamResponse.getHeader(RestConstants.HEADER_CONTENT_TYPE));
                    Assert.assertEquals(contentType.getBaseType(), RestConstants.HEADER_VALUE_MULTIPART_RELATED);
                } catch (ParseException parseException) {
                    Assert.fail();
                }

                final CountDownLatch countDownLatch = new CountDownLatch(1);
                MultiPartMIMEFullReaderCallback fullReaderCallback = new MultiPartMIMEFullReaderCallback(
                        countDownLatch);
                final MultiPartMIMEReader reader = MultiPartMIMEReader.createAndAcquireStream(streamResponse);
                reader.registerReaderCallback(fullReaderCallback);
                try {
                    countDownLatch.await(3000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException interruptedException) {
                    Assert.fail();
                }

                final List<SinglePartMIMEFullReaderCallback> singlePartMIMEReaderCallbacks = fullReaderCallback
                        .getSinglePartMIMEReaderCallbacks();
                Assert.assertEquals(singlePartMIMEReaderCallbacks.size(), 2);

                //Verify first part is Action response.
                Assert.assertEquals(
                        singlePartMIMEReaderCallbacks.get(0).getHeaders().get(RestConstants.HEADER_CONTENT_TYPE),
                        RestConstants.HEADER_VALUE_APPLICATION_JSON);
                Assert.assertEquals(singlePartMIMEReaderCallbacks.get(0).getFinishedData().asAvroString(),
                        "{\"value\":1234}");
                //Verify the second part matches what the server should have sent back
                Assert.assertEquals(
                        singlePartMIMEReaderCallbacks.get(1).getHeaders().get(RestConstants.HEADER_CONTENT_ID),
                        "1");
                Assert.assertEquals(
                        singlePartMIMEReaderCallbacks.get(1).getFinishedData().asString(Charset.defaultCharset()),
                        "one");

                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);
            }

            @Override
            public void onError(Throwable e) {
                fail();
            }
        };

        _server.handleRequest(streamRequest, new RequestContext(), callback);
    }

    @Test
    public void testMultipartRelatedRequestNoUserAttachments() throws Exception {
        //This test verifies the server's ability to handle a multipart related request that has only one part which is
        //the rest.li payload; meaning there are no user defined attachments. Technically the client builders shouldn't do
        //this but we allow this to keep the protocol somewhat flexible.

        final AsyncStatusCollectionResource statusResource = getMockResource(AsyncStatusCollectionResource.class);

        statusResource.streamingAction(EasyMock.<String>anyObject(), EasyMock.<RestLiAttachmentReader>anyObject(),
                EasyMock.<Callback<Long>>anyObject());
        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
            @Override
            public Object answer() throws Throwable {
                //Verify there are no attachments.
                final RestLiAttachmentReader attachmentReader = (RestLiAttachmentReader) EasyMock
                        .getCurrentArguments()[1];
                Assert.assertNull(attachmentReader);

                //Verify the action param.
                Assert.assertEquals((String) EasyMock.getCurrentArguments()[0], "someMetadata");

                //Now respond back to the request.
                @SuppressWarnings("unchecked")
                Callback<Long> callback = (Callback<Long>) EasyMock.getCurrentArguments()[2];
                callback.onSuccess(1234l);
                return null;
            }
        });
        EasyMock.replay(statusResource);

        //Now we create a multipart/related payload.
        final String payload = "{\"metadata\": \"someMetadata\"}";
        final ByteStringWriter byteStringWriter = new ByteStringWriter(
                ByteString.copyString(payload, Charset.defaultCharset()));
        final MultiPartMIMEWriter.Builder builder = new MultiPartMIMEWriter.Builder();
        final MultiPartMIMEWriter writer = AttachmentUtils.createMultiPartMIMEWriter(byteStringWriter,
                "application/json", builder);

        final StreamRequest streamRequest = MultiPartMIMEStreamRequestFactory.generateMultiPartMIMEStreamRequest(
                new URI("/asyncstatuses/?action=streamingAction"), "related", writer,
                Collections.<String, String>emptyMap(), "POST",
                ImmutableMap.of(RestConstants.HEADER_ACCEPT, RestConstants.HEADER_VALUE_MULTIPART_RELATED),
                Collections.emptyList());

        final Callback<StreamResponse> callback = new Callback<StreamResponse>() {
            @Override
            public void onSuccess(StreamResponse streamResponse) {
                Messages.toRestResponse(streamResponse, new Callback<RestResponse>() {
                    @Override
                    public void onError(Throwable e) {
                        Assert.fail();
                    }

                    @Override
                    public void onSuccess(RestResponse result) {
                        Assert.assertEquals(result.getStatus(), 200);

                        try {
                            ContentType contentType = new ContentType(
                                    streamResponse.getHeader(RestConstants.HEADER_CONTENT_TYPE));
                            Assert.assertEquals(contentType.getBaseType(),
                                    RestConstants.HEADER_VALUE_APPLICATION_JSON);
                        } catch (ParseException parseException) {
                            Assert.fail();
                        }

                        //Verify the response body
                        Assert.assertEquals(result.getEntity().asAvroString(), "{\"value\":1234}");

                        EasyMock.verify(statusResource);
                        EasyMock.reset(statusResource);
                    }
                });
            }

            @Override
            public void onError(Throwable e) {
                fail();
            }
        };

        _server.handleRequest(streamRequest, new RequestContext(), callback);
    }

    @Test
    public void testMultipartRelatedNoAttachmentsAtAll() throws Exception {
        //This test verifies the server's ability to throw an exception if there are absolutely no attachments at all
        //in the request. The protocol allows no user attachments to be required, but there must always be at least a rest.li
        //payload in the first part.

        final MultiPartMIMEWriter.Builder builder = new MultiPartMIMEWriter.Builder();

        final StreamRequest streamRequest = MultiPartMIMEStreamRequestFactory.generateMultiPartMIMEStreamRequest(
                new URI("/doesNotMatter"), "related", builder.build(), Collections.<String, String>emptyMap(),
                "POST", ImmutableMap.of(RestConstants.HEADER_ACCEPT, RestConstants.HEADER_VALUE_MULTIPART_RELATED),
                Collections.emptyList());

        final Callback<StreamResponse> callback = new Callback<StreamResponse>() {
            @Override
            public void onSuccess(StreamResponse streamResponse) {
                Assert.fail();
            }

            @Override
            public void onError(Throwable e) {
                //Verify the exception.
                assertTrue(e instanceof StreamException);
                StreamException streamException = (StreamException) e;
                StreamResponse streamResponse = streamException.getResponse();

                assertEquals(streamResponse.getStatus(), 400);
                final FullEntityReader fullEntityReader = new FullEntityReader(new Callback<ByteString>() {
                    @Override
                    public void onError(Throwable e) {
                        Assert.fail();
                    }

                    @Override
                    public void onSuccess(ByteString result) {
                        //We have the body so assert that the exception made it.
                        assertTrue(result.length() > 0);
                        assertTrue(result.asString(Charset.defaultCharset())
                                .contains("Did not receive any parts in the multipart mime request"));
                    }
                });
                streamResponse.getEntityStream().setReader(fullEntityReader);
            }
        };

        _server.handleRequest(streamRequest, new RequestContext(), callback);
    }

    @Test
    public void testRequestAttachmentsResponseAttachmentsException() throws Exception {
        //This test verifies the server's behavior in the face of an exception. In this case the resource method
        //threw an exception AFTER setting response attachments. Additionally the resource method failed to absorb any
        //incoming request attachments. We verify in this test that StreamResponseCallbackAdaptor in RestLiServer
        //drains and absorbs all bytes from the incoming request and that the response attachments set by the resource method
        //are told to abort.

        //Define the server side resource attachments to be sent back.
        final RestLiResponseAttachments.Builder responseAttachmentsBuilder = new RestLiResponseAttachments.Builder();
        final RestLiTestAttachmentDataSource toBeAbortedDataSource = RestLiTestAttachmentDataSource
                .createWithRandomPayload("1");

        responseAttachmentsBuilder.appendSingleAttachment(toBeAbortedDataSource);

        Capture<ResourceContext> resourceContextCapture = new Capture<ResourceContext>();
        final AsyncStatusCollectionResource statusResource = getMockResource(AsyncStatusCollectionResource.class,
                EasyMock.capture(resourceContextCapture));

        statusResource.streamingAction(EasyMock.<String>anyObject(), EasyMock.<RestLiAttachmentReader>anyObject(),
                EasyMock.<Callback<Long>>anyObject());
        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
            @Override
            public Object answer() throws Throwable {
                //Verify there are still attachments to be read.
                final RestLiAttachmentReader attachmentReader = (RestLiAttachmentReader) EasyMock
                        .getCurrentArguments()[1];
                Assert.assertFalse(attachmentReader.haveAllAttachmentsFinished());

                //Verify the action param.
                Assert.assertEquals((String) EasyMock.getCurrentArguments()[0], "someMetadata");

                //Set the response attachments
                resourceContextCapture.getValue().setResponseAttachments(responseAttachmentsBuilder.build());

                //Now throw an exception.
                @SuppressWarnings("unchecked")
                Callback<Long> callback = (Callback<Long>) EasyMock.getCurrentArguments()[2];
                callback.onError(new RestLiServiceException(HttpStatus.S_409_CONFLICT, "Some conflict"));
                return null;
            }
        });
        EasyMock.replay(statusResource);

        //Now we create a multipart/related payload.
        final String payload = "{\"metadata\": \"someMetadata\"}";
        final ByteStringWriter byteStringWriter = new ByteStringWriter(
                ByteString.copyString(payload, Charset.defaultCharset()));
        final MultiPartMIMEWriter.Builder builder = new MultiPartMIMEWriter.Builder();
        final RestLiTestAttachmentDataSource toBeDrainedDataSource = RestLiTestAttachmentDataSource
                .createWithRandomPayload("2");

        AttachmentUtils.appendSingleAttachmentToBuilder(builder, toBeDrainedDataSource);
        final MultiPartMIMEWriter writer = AttachmentUtils.createMultiPartMIMEWriter(byteStringWriter,
                "application/json", builder);

        final StreamRequest streamRequest = MultiPartMIMEStreamRequestFactory.generateMultiPartMIMEStreamRequest(
                new URI("/asyncstatuses/?action=streamingAction"), "related", writer,
                Collections.<String, String>emptyMap(), "POST",
                ImmutableMap.of(RestConstants.HEADER_ACCEPT, RestConstants.HEADER_VALUE_MULTIPART_RELATED),
                Collections.emptyList());

        final Callback<StreamResponse> callback = new Callback<StreamResponse>() {
            @Override
            public void onSuccess(StreamResponse streamResponse) {
                fail();
            }

            @Override
            public void onError(Throwable e) {
                //Verify the exception.
                assertTrue(e instanceof StreamException);
                StreamException streamException = (StreamException) e;
                StreamResponse streamResponse = streamException.getResponse();

                assertEquals(streamResponse.getStatus(), 409);
                final FullEntityReader fullEntityReader = new FullEntityReader(new Callback<ByteString>() {
                    @Override
                    public void onError(Throwable e) {
                        Assert.fail();
                    }

                    @Override
                    public void onSuccess(ByteString result) {
                        //We have the body so assert exception made it.
                        assertTrue(result.length() > 0);
                        assertTrue(result.asString(Charset.defaultCharset()).contains("Some conflict"));
                    }
                });
                streamResponse.getEntityStream().setReader(fullEntityReader);

                EasyMock.verify(statusResource);
                EasyMock.reset(statusResource);
            }
        };

        _server.handleRequest(streamRequest, new RequestContext(), callback);

        //Verify that the request level attachments were drained.
        Assert.assertTrue(toBeDrainedDataSource.finished());

        //Verify that response attachments were told to abort.
        Assert.assertTrue(toBeAbortedDataSource.dataSourceAborted());
    }

    private <R extends BaseResource> R getMockResource(Class<R> resourceClass) {
        return getMockResource(resourceClass, EasyMock.anyObject());
    }

    private <R extends BaseResource> R getMockResource(Class<R> resourceClass, ResourceContext resourceContext) {
        R resource = _resourceFactory.getMock(resourceClass);
        EasyMock.reset(resource);
        resource.setContext(resourceContext);
        EasyMock.expectLastCall().once();

        return resource;
    }

    private Status buildStatusRecord() {
        DataMap map = new DataMap();
        map.put("text", "test status");
        Status status = new Status(map);
        return status;
    }
}