/**
* Copyright 2016 LinkedIn Corp. All rights reserved.
*
* 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.
*/
package com.github.ambry.admin;
import com.codahale.metrics.MetricRegistry;
import com.github.ambry.config.AdminConfig;
import com.github.ambry.config.VerifiableProperties;
import com.github.ambry.messageformat.BlobInfo;
import com.github.ambry.messageformat.BlobProperties;
import com.github.ambry.rest.MockRestResponseChannel;
import com.github.ambry.rest.ResponseStatus;
import com.github.ambry.rest.RestMethod;
import com.github.ambry.rest.RestRequest;
import com.github.ambry.rest.RestResponseChannel;
import com.github.ambry.rest.RestServiceErrorCode;
import com.github.ambry.rest.RestServiceException;
import com.github.ambry.rest.RestUtils;
import com.github.ambry.rest.SecurityService;
import com.github.ambry.router.Callback;
import com.github.ambry.utils.Utils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.Assert;
import org.json.JSONObject;
import org.junit.Test;
/**
* Unit tests {@link AdminSecurityService}
*/
public class AdminSecurityServiceTest {
private static final AdminConfig ADMIN_CONFIG = new AdminConfig(new VerifiableProperties(new Properties()));
private static final String SERVICE_ID = "AdminSecurityService";
private static final String OWNER_ID = SERVICE_ID;
private static final BlobInfo DEFAULT_INFO =
new BlobInfo(new BlobProperties(100, SERVICE_ID, OWNER_ID, "image/gif", true, Utils.Infinite_Time), null);
private final SecurityService securityService =
new AdminSecurityService(ADMIN_CONFIG, new AdminMetrics(new MetricRegistry()));
/**
* Tests {@link AdminSecurityService#processRequest(RestRequest, Callback)} for common as well as uncommon cases
* @throws Exception
*/
@Test
public void processRequestTest() throws Exception {
SecurityServiceCallback callback = new SecurityServiceCallback();
//rest request being null
try {
securityService.processRequest(null, callback).get();
Assert.fail("Should have thrown IllegalArgumentException ");
} catch (IllegalArgumentException e) {
}
// without callbacks
RestMethod[] methods = new RestMethod[]{RestMethod.GET, RestMethod.DELETE, RestMethod.HEAD};
for (RestMethod restMethod : methods) {
RestRequest restRequest = AdminTestUtils.createRestRequest(restMethod, "/", null, null);
securityService.processRequest(restRequest, null).get();
}
// with callbacks
callback = new SecurityServiceCallback();
for (RestMethod restMethod : methods) {
RestRequest restRequest = AdminTestUtils.createRestRequest(restMethod, "/", null, null);
securityService.processRequest(restRequest, callback).get();
Assert.assertTrue("Callback should have been invoked", callback.callbackLatch.await(1, TimeUnit.SECONDS));
Assert.assertNull("Exception should not have been thrown", callback.exception);
callback.reset();
}
// security service closed
callback = new SecurityServiceCallback();
securityService.close();
for (RestMethod restMethod : methods) {
RestRequest restRequest = AdminTestUtils.createRestRequest(restMethod, "/", null, null);
try {
securityService.processRequest(restRequest, callback).get();
Assert.fail("Process Request should have failed because Security Service is closed");
} catch (ExecutionException e) {
Assert.assertTrue("Exception should have been an instance of RestServiceException",
e.getCause() instanceof RestServiceException);
RestServiceException re = (RestServiceException) e.getCause();
Assert.assertEquals("Unexpected RestServerErrorCode (Future)", RestServiceErrorCode.ServiceUnavailable,
re.getErrorCode());
re = (RestServiceException) callback.exception;
Assert.assertEquals("Unexpected RestServerErrorCode (Callback)", RestServiceErrorCode.ServiceUnavailable,
re.getErrorCode());
}
callback.reset();
}
}
/**
* Tests {@link AdminSecurityService#processResponse(RestRequest, RestResponseChannel, BlobInfo, Callback)} for
* common as well as uncommon cases
* @throws Exception
*/
@Test
public void processResponseTest() throws Exception {
RestRequest restRequest = AdminTestUtils.createRestRequest(RestMethod.GET, "/", null, null);
//rest request being null
try {
securityService.processResponse(null, new MockRestResponseChannel(), DEFAULT_INFO, null).get();
Assert.fail("Should have thrown IllegalArgumentException ");
} catch (IllegalArgumentException e) {
}
//restResponseChannel being null
try {
securityService.processResponse(restRequest, null, DEFAULT_INFO, null).get();
Assert.fail("Should have thrown IllegalArgumentException ");
} catch (IllegalArgumentException e) {
}
//blob info being null
try {
securityService.processResponse(restRequest, new MockRestResponseChannel(), null, null).get();
Assert.fail("Should have thrown IllegalArgumentException ");
} catch (IllegalArgumentException e) {
}
// without callbacks
RestMethod[] methods = new RestMethod[]{RestMethod.GET, RestMethod.HEAD};
for (RestMethod restMethod : methods) {
restRequest = AdminTestUtils.createRestRequest(restMethod, "/", null, null);
securityService.processResponse(restRequest, new MockRestResponseChannel(), DEFAULT_INFO, null).get();
}
// with callbacks
// for unsupported methods
methods = new RestMethod[]{RestMethod.DELETE};
for (RestMethod restMethod : methods) {
testExceptionCasesProcessResponse(restMethod, new MockRestResponseChannel(), DEFAULT_INFO,
RestServiceErrorCode.InternalServerError);
}
// HEAD
// normal
testHeadBlob(DEFAULT_INFO);
// with no owner id
BlobInfo blobInfo =
new BlobInfo(new BlobProperties(100, SERVICE_ID, null, "image/gif", false, Utils.Infinite_Time), null);
testHeadBlob(blobInfo);
// with no content type
blobInfo = new BlobInfo(new BlobProperties(100, SERVICE_ID, OWNER_ID, null, false, Utils.Infinite_Time), null);
testHeadBlob(blobInfo);
// with a TTL
blobInfo = new BlobInfo(new BlobProperties(100, SERVICE_ID, OWNER_ID, "image/gif", false, 10000), null);
testHeadBlob(blobInfo);
// GET BlobInfo
testGetSubResource(RestUtils.SubResource.BlobInfo);
// GET UserMetadata
testGetSubResource(RestUtils.SubResource.UserMetadata);
// GET Blob
// less than chunk threshold size
blobInfo = new BlobInfo(
new BlobProperties(ADMIN_CONFIG.adminChunkedGetResponseThresholdInBytes - 1, SERVICE_ID, OWNER_ID, "image/gif",
false, 10000), null);
testGetBlob(blobInfo);
// == chunk threshold size
blobInfo = new BlobInfo(
new BlobProperties(ADMIN_CONFIG.adminChunkedGetResponseThresholdInBytes, SERVICE_ID, OWNER_ID, "image/gif",
false, 10000), null);
testGetBlob(blobInfo);
// more than chunk threshold size
blobInfo = new BlobInfo(
new BlobProperties(ADMIN_CONFIG.adminChunkedGetResponseThresholdInBytes * 2, SERVICE_ID, OWNER_ID, "image/gif",
false, 10000), null);
testGetBlob(blobInfo);
// Get blob with content type null
blobInfo = new BlobInfo(new BlobProperties(100, SERVICE_ID, OWNER_ID, null, true, 10000), null);
testGetBlob(blobInfo);
// Get blob for a private blob
blobInfo =
new BlobInfo(new BlobProperties(100, SERVICE_ID, OWNER_ID, "image/gif", false, Utils.Infinite_Time), null);
testGetBlob(blobInfo);
// not modified response
// > creation time (in secs).
testGetNotModifiedBlob(blobInfo, blobInfo.getBlobProperties().getCreationTimeInMs() + 1000);
// == creation time
testGetNotModifiedBlob(blobInfo, blobInfo.getBlobProperties().getCreationTimeInMs());
// < creation time (in secs)
testGetNotModifiedBlob(blobInfo, blobInfo.getBlobProperties().getCreationTimeInMs() - 1000);
// Get blob for a public blob with content type as "text/html"
blobInfo = new BlobInfo(new BlobProperties(100, SERVICE_ID, OWNER_ID, "text/html", true, 10000), null);
testGetBlob(blobInfo);
// not modified response
// > creation time (in secs).
testGetNotModifiedBlob(DEFAULT_INFO, DEFAULT_INFO.getBlobProperties().getCreationTimeInMs() + 1000);
// == creation time
testGetNotModifiedBlob(DEFAULT_INFO, DEFAULT_INFO.getBlobProperties().getCreationTimeInMs());
// < creation time (in secs)
testGetNotModifiedBlob(DEFAULT_INFO, DEFAULT_INFO.getBlobProperties().getCreationTimeInMs() - 1000);
// bad rest response channel
testExceptionCasesProcessResponse(RestMethod.HEAD, new BadRestResponseChannel(), blobInfo,
RestServiceErrorCode.InternalServerError);
testExceptionCasesProcessResponse(RestMethod.GET, new BadRestResponseChannel(), blobInfo,
RestServiceErrorCode.InternalServerError);
// security service closed
securityService.close();
methods = new RestMethod[]{RestMethod.GET, RestMethod.DELETE, RestMethod.HEAD};
for (RestMethod restMethod : methods) {
testExceptionCasesProcessResponse(restMethod, new MockRestResponseChannel(), blobInfo,
RestServiceErrorCode.ServiceUnavailable);
}
}
/**
* Verifies that there are no values for all headers in {@code headers}.
* @param restResponseChannel the {@link MockRestResponseChannel} over which response has been received.
* @param headers the headers that must have no values.
*/
private void verifyAbsenceOfHeaders(MockRestResponseChannel restResponseChannel, String... headers) {
for (String header : headers) {
Assert.assertNull("[" + header + "] should not have been present in the response",
restResponseChannel.getHeader(header));
}
}
/**
* Tests {@link SecurityService#processResponse(RestRequest, RestResponseChannel, BlobInfo, Callback)} for a Get blob
* with the passed in {@link BlobInfo}
* @param blobInfo the {@link BlobInfo} to be used for the {@link RestRequest}
* @throws Exception
*/
private void testGetBlob(BlobInfo blobInfo) throws Exception {
SecurityServiceCallback callback = new SecurityServiceCallback();
MockRestResponseChannel restResponseChannel = new MockRestResponseChannel();
RestRequest restRequest = AdminTestUtils.createRestRequest(RestMethod.GET, "/", null, null);
securityService.processResponse(restRequest, restResponseChannel, blobInfo, callback).get();
Assert.assertTrue("Callback should have been invoked", callback.callbackLatch.await(1, TimeUnit.SECONDS));
Assert.assertNull("Exception should not have been thrown", callback.exception);
Assert.assertEquals("Response should have been set ", ResponseStatus.Ok, restResponseChannel.getStatus());
verifyHeadersForGetBlob(blobInfo.getBlobProperties(), restResponseChannel);
}
/**
* Tests {@link SecurityService#processResponse(RestRequest, RestResponseChannel, BlobInfo, Callback)} for a Get blob
* with the passed in {@link BlobInfo} for a not modified response
* @param blobInfo the {@link BlobInfo} to be used for the {@link RestRequest}
* @param ifModifiedSinceMs the value (as a date string) of the {@link RestUtils.Headers#IF_MODIFIED_SINCE} header.
* @throws Exception
*/
private void testGetNotModifiedBlob(BlobInfo blobInfo, long ifModifiedSinceMs) throws Exception {
SecurityServiceCallback callback = new SecurityServiceCallback();
MockRestResponseChannel restResponseChannel = new MockRestResponseChannel();
JSONObject headers = new JSONObject();
SimpleDateFormat dateFormat = new SimpleDateFormat(RestUtils.HTTP_DATE_FORMAT, Locale.ENGLISH);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date date = new Date(ifModifiedSinceMs);
String dateStr = dateFormat.format(date);
headers.put(RestUtils.Headers.IF_MODIFIED_SINCE, dateStr);
RestRequest restRequest = AdminTestUtils.createRestRequest(RestMethod.GET, "/abc", headers, null);
securityService.processResponse(restRequest, restResponseChannel, blobInfo, callback).get();
Assert.assertTrue("Callback should have been invoked", callback.callbackLatch.await(1, TimeUnit.SECONDS));
Assert.assertNull("Exception should not have been thrown", callback.exception);
if (ifModifiedSinceMs >= blobInfo.getBlobProperties().getCreationTimeInMs()) {
Assert.assertEquals("Not modified response expected", ResponseStatus.NotModified,
restResponseChannel.getStatus());
verifyHeadersForGetBlobNotModified(restResponseChannel, blobInfo.getBlobProperties().isPrivate());
} else {
Assert.assertEquals("Not modified response should not be returned", ResponseStatus.Ok,
restResponseChannel.getStatus());
verifyHeadersForGetBlob(blobInfo.getBlobProperties(), restResponseChannel);
}
}
/**
* Tests {@link AdminSecurityService#processResponse(RestRequest, RestResponseChannel, BlobInfo, Callback)} for
* {@link RestMethod#HEAD}.
* @param blobInfo the {@link BlobInfo} of the blob for which {@link RestMethod#HEAD} is required.
* @throws Exception
*/
private void testHeadBlob(BlobInfo blobInfo) throws Exception {
SecurityServiceCallback callback = new SecurityServiceCallback();
MockRestResponseChannel restResponseChannel = new MockRestResponseChannel();
RestRequest restRequest = AdminTestUtils.createRestRequest(RestMethod.HEAD, "/", null, null);
securityService.processResponse(restRequest, restResponseChannel, blobInfo, callback).get();
Assert.assertTrue("Callback should have been invoked", callback.callbackLatch.await(1, TimeUnit.SECONDS));
Assert.assertNull("Exception should not have been thrown", callback.exception);
Assert.assertEquals("Response status should have been set ", ResponseStatus.Ok, restResponseChannel.getStatus());
verifyHeadersForHead(blobInfo.getBlobProperties(), restResponseChannel);
}
/**
* Tests GET of sub-resources.
* @param subResource the {@link RestUtils.SubResource} to test.
* @throws Exception
*/
private void testGetSubResource(RestUtils.SubResource subResource) throws Exception {
SecurityServiceCallback callback = new SecurityServiceCallback();
MockRestResponseChannel restResponseChannel = new MockRestResponseChannel();
RestRequest restRequest = AdminTestUtils.createRestRequest(RestMethod.GET, "/sampleId/" + subResource, null, null);
securityService.processResponse(restRequest, restResponseChannel, DEFAULT_INFO, callback).get();
Assert.assertTrue("Callback should have been invoked", callback.callbackLatch.await(1, TimeUnit.SECONDS));
Assert.assertNull("Exception should not have been thrown", callback.exception);
Assert.assertEquals("Response status should have been set ", ResponseStatus.Ok, restResponseChannel.getStatus());
Assert.assertNotNull("Date has not been set", restResponseChannel.getHeader(RestUtils.Headers.DATE));
Assert.assertEquals("Last Modified does not match creation time",
RestUtils.toSecondsPrecisionInMs(DEFAULT_INFO.getBlobProperties().getCreationTimeInMs()),
RestUtils.getTimeFromDateString(restResponseChannel.getHeader(RestUtils.Headers.LAST_MODIFIED)).longValue());
if (subResource.equals(RestUtils.SubResource.BlobInfo)) {
verifyBlobPropertiesHeaders(DEFAULT_INFO.getBlobProperties(), restResponseChannel);
} else {
verifyAbsenceOfHeaders(restResponseChannel, RestUtils.Headers.PRIVATE, RestUtils.Headers.TTL,
RestUtils.Headers.SERVICE_ID, RestUtils.Headers.OWNER_ID, RestUtils.Headers.AMBRY_CONTENT_TYPE,
RestUtils.Headers.CREATION_TIME, RestUtils.Headers.BLOB_SIZE);
}
}
/**
* Tests exception cases for {@link SecurityService#processResponse(RestRequest, RestResponseChannel, BlobInfo, Callback)}
* with a {@link BadRestResponseChannel}
* @param restMethod the {@link RestMethod} of the request to be made
* @param restResponseChannel the {@link RestResponseChannel} to write responses over.
* @param blobInfo the {@link BlobInfo} to be used for the {@link RestRequest}
* @param expectedErrorCode the {@link RestServiceErrorCode} expected in the exception returned.
* @throws Exception
*/
private void testExceptionCasesProcessResponse(RestMethod restMethod, RestResponseChannel restResponseChannel,
BlobInfo blobInfo, RestServiceErrorCode expectedErrorCode) throws Exception {
RestRequest restRequest = AdminTestUtils.createRestRequest(restMethod, "/", null, null);
SecurityServiceCallback callback = new SecurityServiceCallback();
try {
securityService.processResponse(restRequest, restResponseChannel, blobInfo, callback).get();
Assert.fail("Should have thrown Exception");
} catch (ExecutionException e) {
Assert.assertTrue("Exception should have been an instance of RestServiceException",
e.getCause() instanceof RestServiceException);
RestServiceException re = (RestServiceException) e.getCause();
Assert.assertEquals("Unexpected RestServerErrorCode (Future)", expectedErrorCode, re.getErrorCode());
Assert.assertTrue("Callback should have been invoked", callback.callbackLatch.await(1, TimeUnit.SECONDS));
Assert.assertNotNull("Exception should have been thrown", callback.exception);
re = (RestServiceException) callback.exception;
Assert.assertEquals("Unexpected RestServerErrorCode (Callback)", expectedErrorCode, re.getErrorCode());
}
}
/**
* Verify the headers from the response are as expected
* @param blobProperties the {@link BlobProperties} to refer to while getting headers.
* @param restResponseChannel {@link MockRestResponseChannel} from which headers are to be verified
* @throws RestServiceException if there was any problem getting the headers.
*/
private void verifyHeadersForHead(BlobProperties blobProperties, MockRestResponseChannel restResponseChannel)
throws RestServiceException {
Assert.assertNotNull("Date has not been set", restResponseChannel.getHeader(RestUtils.Headers.DATE));
Assert.assertEquals("Last Modified does not match creation time",
RestUtils.toSecondsPrecisionInMs(blobProperties.getCreationTimeInMs()),
RestUtils.getTimeFromDateString(restResponseChannel.getHeader(RestUtils.Headers.LAST_MODIFIED)).longValue());
Assert.assertEquals("Content length mismatch", blobProperties.getBlobSize(),
Long.parseLong(restResponseChannel.getHeader(RestUtils.Headers.CONTENT_LENGTH)));
if (blobProperties.getContentType() != null) {
Assert.assertEquals("Content Type mismatch", blobProperties.getContentType(),
restResponseChannel.getHeader(RestUtils.Headers.CONTENT_TYPE));
}
verifyBlobPropertiesHeaders(blobProperties, restResponseChannel);
}
/**
* Verify the headers from the response are as expected
* @param blobProperties the {@link BlobProperties} to refer to while getting headers.
* @param restResponseChannel {@link MockRestResponseChannel} from which headers are to be verified
* @throws RestServiceException if there was any problem getting the headers.
*/
private void verifyHeadersForGetBlob(BlobProperties blobProperties, MockRestResponseChannel restResponseChannel)
throws RestServiceException {
Assert.assertEquals("Blob size mismatch ", blobProperties.getBlobSize(),
Long.parseLong(restResponseChannel.getHeader(RestUtils.Headers.BLOB_SIZE)));
verifyAbsenceOfHeaders(restResponseChannel, RestUtils.Headers.PRIVATE, RestUtils.Headers.TTL,
RestUtils.Headers.SERVICE_ID, RestUtils.Headers.OWNER_ID, RestUtils.Headers.AMBRY_CONTENT_TYPE,
RestUtils.Headers.CREATION_TIME);
if (blobProperties.getContentType() != null) {
Assert.assertEquals("Content Type mismatch", blobProperties.getContentType(),
restResponseChannel.getHeader(RestUtils.Headers.CONTENT_TYPE));
if (blobProperties.getContentType().equals("text/html")) {
Assert.assertEquals("Content disposition not set for text/html Cotnent type", "attachment",
restResponseChannel.getHeader("Content-Disposition"));
} else {
Assert.assertNull("Content disposition should not have been set",
restResponseChannel.getHeader("Content-Disposition"));
}
}
if (blobProperties.getBlobSize() < ADMIN_CONFIG.adminChunkedGetResponseThresholdInBytes) {
Assert.assertEquals("Content length value mismatch", blobProperties.getBlobSize(),
Integer.parseInt(restResponseChannel.getHeader(RestUtils.Headers.CONTENT_LENGTH)));
} else {
Assert.assertNull("Content length value should not be set",
restResponseChannel.getHeader(RestUtils.Headers.CONTENT_LENGTH));
}
verifyCacheHeaders(blobProperties.isPrivate(), restResponseChannel);
}
/**
* Verify the headers from the response for a Not modified blob are as expected
* @param restResponseChannel {@link MockRestResponseChannel} from which headers are to be verified
* @param isPrivate {@code true} if blob is private, {@code false} otherwise.
* @throws RestServiceException if there was any problem getting the headers.
*/
private void verifyHeadersForGetBlobNotModified(MockRestResponseChannel restResponseChannel, boolean isPrivate)
throws RestServiceException {
Assert.assertNotNull("Date has not been set", restResponseChannel.getHeader(RestUtils.Headers.DATE));
Assert.assertNotNull("Last-Modified has not been set",
restResponseChannel.getHeader(RestUtils.Headers.LAST_MODIFIED));
Assert.assertNull("Content length should not have been set",
restResponseChannel.getHeader(RestUtils.Headers.CONTENT_LENGTH));
verifyCacheHeaders(isPrivate, restResponseChannel);
verifyAbsenceOfHeaders(restResponseChannel, RestUtils.Headers.BLOB_SIZE, RestUtils.Headers.CONTENT_TYPE);
}
/**
* Verifies that the right cache headers are returned.
* @param isPrivate {@code true} if the blob is private, {@code false} if not.
* @param restResponseChannel the {@link RestResponseChannel} over which the response is sent.
*/
private void verifyCacheHeaders(boolean isPrivate, MockRestResponseChannel restResponseChannel) {
if (isPrivate) {
Assert.assertEquals("Expires value is incorrect for private blob",
restResponseChannel.getHeader(RestUtils.Headers.DATE),
restResponseChannel.getHeader(RestUtils.Headers.EXPIRES));
Assert.assertEquals("Cache-Control value not as expected", "private, no-cache, no-store, proxy-revalidate",
restResponseChannel.getHeader(RestUtils.Headers.CACHE_CONTROL));
Assert.assertEquals("Pragma value not as expected", "no-cache",
restResponseChannel.getHeader(RestUtils.Headers.PRAGMA));
} else {
Assert.assertTrue("Expires value should be in the future",
RestUtils.getTimeFromDateString(restResponseChannel.getHeader(RestUtils.Headers.EXPIRES))
> System.currentTimeMillis());
Assert.assertEquals("Cache-Control value not as expected", "max-age=" + ADMIN_CONFIG.adminCacheValiditySeconds,
restResponseChannel.getHeader(RestUtils.Headers.CACHE_CONTROL));
Assert.assertNull("Pragma value should not have been set",
restResponseChannel.getHeader(RestUtils.Headers.PRAGMA));
}
}
/**
* Verify the headers from the response are as expected
* @param blobProperties the {@link BlobProperties} to refer to while getting headers.
* @param restResponseChannel {@link MockRestResponseChannel} from which headers are to be verified
* @throws RestServiceException if there was any problem getting the headers.
*/
private void verifyBlobPropertiesHeaders(BlobProperties blobProperties, MockRestResponseChannel restResponseChannel)
throws RestServiceException {
if (blobProperties.getContentType() != null) {
Assert.assertEquals("Ambry Content Type mismatch", blobProperties.getContentType(),
restResponseChannel.getHeader(RestUtils.Headers.AMBRY_CONTENT_TYPE));
}
Assert.assertEquals("Blob Size mismatch", blobProperties.getBlobSize(),
Long.parseLong(restResponseChannel.getHeader(RestUtils.Headers.BLOB_SIZE)));
Assert.assertEquals("Service Id mismatch", blobProperties.getServiceId(),
restResponseChannel.getHeader(RestUtils.Headers.SERVICE_ID));
Assert.assertEquals("Creation time should have been set correctly",
RestUtils.toSecondsPrecisionInMs(blobProperties.getCreationTimeInMs()),
RestUtils.getTimeFromDateString(restResponseChannel.getHeader(RestUtils.Headers.CREATION_TIME)).longValue());
Assert.assertEquals("Private value mismatch", blobProperties.isPrivate(),
Boolean.parseBoolean(restResponseChannel.getHeader(RestUtils.Headers.PRIVATE)));
if (blobProperties.getTimeToLiveInSeconds() != Utils.Infinite_Time) {
Assert.assertEquals("TTL mismatch", blobProperties.getTimeToLiveInSeconds(),
Long.parseLong(restResponseChannel.getHeader(RestUtils.Headers.TTL)));
}
if (blobProperties.getOwnerId() != null) {
Assert.assertEquals("OwnerId mismatch", blobProperties.getOwnerId(),
restResponseChannel.getHeader(RestUtils.Headers.OWNER_ID));
}
}
/**
* Callback for all operations on {@link SecurityService}.
*/
class SecurityServiceCallback implements Callback<Void> {
public volatile Exception exception;
public CountDownLatch callbackLatch = new CountDownLatch(1);
private final AtomicBoolean callbackInvoked = new AtomicBoolean(false);
@Override
public void onCompletion(Void result, Exception exception) {
if (callbackInvoked.compareAndSet(false, true)) {
this.exception = exception;
callbackLatch.countDown();
} else {
this.exception = new IllegalStateException("Callback invoked more than once");
}
}
/**
* Resets the state for using this instance again.
*/
public void reset() {
exception = null;
callbackInvoked.set(false);
callbackLatch = new CountDownLatch(1);
}
}
/**
* A bad implementation of {@link RestResponseChannel}. Just throws exceptions.
*/
class BadRestResponseChannel implements RestResponseChannel {
@Override
public Future<Long> write(ByteBuffer src, Callback<Long> callback) {
return null;
}
@Override
public boolean isOpen() {
return false;
}
@Override
public void close() throws IOException {
}
@Override
public void onResponseComplete(Exception exception) {
}
@Override
public void setStatus(ResponseStatus status) throws RestServiceException {
throw new RestServiceException("Not Implemented", RestServiceErrorCode.InternalServerError);
}
@Override
public ResponseStatus getStatus() {
throw new IllegalStateException("Not implemented");
}
@Override
public void setHeader(String headerName, Object headerValue) throws RestServiceException {
throw new RestServiceException("Not Implemented", RestServiceErrorCode.InternalServerError);
}
@Override
public Object getHeader(String headerName) {
throw new IllegalStateException("Not implemented");
}
}
}