/*
* Copyright 2010-2015 Amazon.com, Inc. or its affiliates. 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://aws.amazon.com/apache2.0
*
* This file 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.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper;
import static org.easymock.EasyMock.anyObject;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.HashKeyAutoGenerated;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapper.FailedBatch;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapper.SaveObjectHandler;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapperConfig.PaginationLoadingStrategy;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodbv2.model.BatchGetItemRequest;
import com.amazonaws.services.dynamodbv2.model.BatchGetItemResult;
import com.amazonaws.services.dynamodbv2.model.BatchWriteItemRequest;
import com.amazonaws.services.dynamodbv2.model.BatchWriteItemResult;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue;
import com.amazonaws.services.dynamodbv2.model.KeysAndAttributes;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.WriteRequest;
import com.amazonaws.util.StringUtils;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DynamoDBMapperTest {
private AmazonDynamoDB mockClient;
private DynamoDBMapper mapper;
private final PaginationLoadingStrategy strategy = PaginationLoadingStrategy.LAZY_LOADING;
private DynamoDBMapperConfig config;
@Before
public void setup() {
config = new DynamoDBMapperConfig(strategy);
mockClient = EasyMock.createMock(AmazonDynamoDBClient.class);
mapper = new DynamoDBMapper(mockClient);
}
@Test
public void testCreateKeyObjectTest() {
IndexRangeKeyClass keyClass = mapper.createKeyObject(IndexRangeKeyClass.class,
Long.parseLong("5"), Double.parseDouble("9"));
assertEquals(keyClass.getKey(), 5L);
assertEquals(keyClass.getRangeKey(), 9.0, .005);
}
@Test(expected = DynamoDBMappingException.class)
public void testCreateKeyObjectTestNoHashKeyAnnotation() {
TestClass keyClass = mapper.createKeyObject(TestClass.class, Long.parseLong("5"),
Double.parseDouble("9"));
}
@Test(expected = DynamoDBMappingException.class)
public void testCreateKeyObjectTestNoRangeKeyAnnotation() {
MockTwoValuePlusVersionClass keyClass = mapper.createKeyObject(
MockTwoValuePlusVersionClass.class, "Hash", "Range");
}
@Test
public void testTransformAttributeUpdates() {
mockClient = EasyMock.createMock(AmazonDynamoDBClient.class);
mapper = new DynamoDBMapper(mockClient, config, new AttributeTransformer() {
@Override
public Map<String, AttributeValue> transform(Parameters<?> parameters) {
Map<String, AttributeValue> upperCased = new HashMap<String, AttributeValue>();
for (Map.Entry<String, AttributeValue> curr : parameters.getAttributeValues()
.entrySet()) {
upperCased.put(curr.getKey(),
new AttributeValue().withS(StringUtils.upperCase(curr.getValue().getS())));
}
return upperCased;
}
@Override
public Map<String, AttributeValue> untransform(Parameters<?> parameters) {
// TODO Auto-generated method stub
return null;
}
});
Map<String, AttributeValue> keys = new HashMap<String, AttributeValue>();
keys.put("id", new AttributeValue().withS("hashKey"));
Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
AttributeValueUpdate update = new AttributeValueUpdate().withValue(new AttributeValue()
.withS("newValue1"));
updates.put("firstValue", update);
Map<String, AttributeValueUpdate> transformed = mapper.transformAttributeUpdates(
MockTwoValuePlusVersionClass.class, "aws-android-sdk-dynamodbmapper-test", keys,
updates, config);
assertEquals(transformed.get("firstValue").getValue().getS(), "NEWVALUE1");
assertNull(transformed.get("id"));
}
@Test
public void testWriteOneBatchWithEntityTooLarge() {
Map<String, List<WriteRequest>> batchMap = new HashMap<String, List<WriteRequest>>();
List<WriteRequest> batchList = new ArrayList<WriteRequest>();
WriteRequest wr1 = new WriteRequest();
WriteRequest wr2 = new WriteRequest();
WriteRequest wr3 = new WriteRequest();
batchList.add(wr1);
batchList.add(wr2);
batchList.add(wr3);
batchMap.put("testTable", batchList);
EasyMock.reset(mockClient);
AmazonServiceException ase = new AmazonServiceException("TestException");
ase.setErrorCode("Request entity too large");
BatchWriteItemResult mockResult = EasyMock.createMock(BatchWriteItemResult.class);
EasyMock.reset(mockResult);
EasyMock.expect(mockResult.getUnprocessedItems()).andReturn(
new HashMap<String, List<WriteRequest>>()).times(2);
// Will cause batches to be split and re-tried
EasyMock.expect(mockClient.batchWriteItem(anyObject(BatchWriteItemRequest.class)))
.andThrow(ase);
EasyMock.expect(mockClient.batchWriteItem(anyObject(BatchWriteItemRequest.class)))
.andReturn(mockResult);
EasyMock.expect(mockClient.batchWriteItem(anyObject(BatchWriteItemRequest.class)))
.andReturn(mockResult);
EasyMock.replay(mockClient, mockResult);
List<FailedBatch> result = mapper.writeOneBatch(batchMap);
assertEquals(result.size(), 0);
EasyMock.verify(mockClient);
}
@Test
public void testBatchLoadRetiresForUnprocessedItems() {
List<Object> itemsToGet = new ArrayList<Object>();
itemsToGet.add(new MockTwoValuePlusVersionClass("PrimaryKey",
"Value1", null));
itemsToGet.add(new MockDifferentTableName("OtherPrimaryKey", "OtherValue1"));
EasyMock.reset(mockClient);
// First result will show that the first item was processed
// successfully, and the second item needs to be retried
BatchGetItemResult firstResult = new BatchGetItemResult();
Map<String, List<Map<String, AttributeValue>>> responses = new HashMap<String, List<Map<String, AttributeValue>>>();
List<Map<String, AttributeValue>> items = new ArrayList<Map<String, AttributeValue>>();
Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
item.put("id", new AttributeValue().withS("idValue"));
item.put("firstValue", new AttributeValue().withS("firstValueValue"));
item.put("secondValue", new AttributeValue().withS("secondValueValue"));
item.put("version", new AttributeValue().withN("1"));
items.add(item);
responses.put(mapper.getTableName(MockTwoValuePlusVersionClass.class, config), items);
firstResult.withResponses(responses);
// Do Not process second table on first go around
Map<String, KeysAndAttributes> unprocessedObjects = new HashMap<String, KeysAndAttributes>();
KeysAndAttributes unprocessedObject = new KeysAndAttributes();
Map<String, AttributeValue> unprocessedKey = new HashMap<String, AttributeValue>();
unprocessedKey.put("id", new
AttributeValue().withS("OtherPrimaryKey"));
unprocessedObject.withKeys(unprocessedKey);
unprocessedObjects.put("MockDifferentTableName", unprocessedObject);
firstResult.withUnprocessedKeys(unprocessedObjects);
// EasyMock is broken and will change all captured values to the last
// capture, even if the capture
// objects are different and set to CaptureType.ALL this is used so that
// we can verify arguments
FixedCapture<BatchGetItemRequest> capture = new FixedCapture<BatchGetItemRequest>(
CaptureType.ALL, new FixedCapture.CapCallback<BatchGetItemRequest>() {
@Override
public void valueSet(BatchGetItemRequest value) {
assertEquals(value.getRequestItems().size(), 2);
}
});
EasyMock.expect(mockClient.batchGetItem(EasyMock.capture(capture))).andReturn(
firstResult);
BatchGetItemResult secondResult = new BatchGetItemResult();
Map<String, List<Map<String, AttributeValue>>> secondResponses = new HashMap<String, List<Map<String, AttributeValue>>>();
List<Map<String, AttributeValue>> secondItems = new ArrayList<Map<String, AttributeValue>>();
Map<String, AttributeValue> secondItem = new HashMap<String, AttributeValue>();
secondItem.put("id", new AttributeValue().withS("idValue2"));
secondItem.put("firstValue", new AttributeValue().withS("firstValueValue2"));
secondItems.add(secondItem);
responses.put(mapper.getTableName(MockDifferentTableName.class, config), secondItems);
secondResult.withResponses(secondResponses);
FixedCapture<BatchGetItemRequest> capture2 = new FixedCapture<BatchGetItemRequest>(
CaptureType.ALL, new FixedCapture.CapCallback<BatchGetItemRequest>() {
@Override
public void valueSet(BatchGetItemRequest value) {
assertEquals(value.getRequestItems().size(), 1);
}
});
EasyMock.expect(mockClient.batchGetItem(EasyMock.capture(capture2))).andReturn(
secondResult);
EasyMock.replay(mockClient);
Map<String, List<Object>> loadResults = mapper.batchLoad(itemsToGet);
EasyMock.verify(mockClient);
assertEquals(loadResults.keySet().size(), 2);
assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test").size(), 1);
assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test-different-table").size(),
1);
assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test").get(0).getClass(),
MockTwoValuePlusVersionClass.class);
assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test-different-table").get(0)
.getClass(), MockDifferentTableName.class);
}
@Test
public void testMergeExpectedAttributeValueConditions() {
Map<String, ExpectedAttributeValue> internalAssertions = new HashMap<String, ExpectedAttributeValue>();
Map<String, ExpectedAttributeValue> userProvidedConditions = new HashMap<String, ExpectedAttributeValue>();
ExpectedAttributeValue internal = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("internal"));
ExpectedAttributeValue user = new ExpectedAttributeValue().withValue(new AttributeValue()
.withS("user"));
ExpectedAttributeValue bothInterlan = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("bothInterlan"));
ExpectedAttributeValue bothUser = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("bothUser"));
internalAssertions.put("internal", internal);
userProvidedConditions.put("user", user);
internalAssertions.put("both", bothInterlan);
userProvidedConditions.put("both", bothUser);
Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
.mergeExpectedAttributeValueConditions(
internalAssertions, userProvidedConditions, "AND");
assertEquals(merged.get("internal").getValue().getS(), "internal");
assertEquals(merged.get("user").getValue().getS(), "user");
assertEquals(merged.get("both").getValue().getS(), "bothUser");
}
@Test(expected = IllegalArgumentException.class)
public void testMergeExpectedAttributeValueConditionsInvalidOperator() {
Map<String, ExpectedAttributeValue> internalAssertions = new HashMap<String, ExpectedAttributeValue>();
Map<String, ExpectedAttributeValue> userProvidedConditions = new HashMap<String, ExpectedAttributeValue>();
ExpectedAttributeValue internal = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("internal"));
ExpectedAttributeValue user = new ExpectedAttributeValue().withValue(new AttributeValue()
.withS("user"));
ExpectedAttributeValue bothInterlan = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("bothInterlan"));
ExpectedAttributeValue bothUser = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("bothUser"));
internalAssertions.put("internal", internal);
userProvidedConditions.put("user", user);
internalAssertions.put("both", bothInterlan);
userProvidedConditions.put("both", bothUser);
Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
.mergeExpectedAttributeValueConditions(
internalAssertions, userProvidedConditions, "OR");
}
@Test
public void testMergeExpectedAttributeValueConditionsNoInternalAssertions() {
Map<String, ExpectedAttributeValue> userProvidedConditions = new HashMap<String, ExpectedAttributeValue>();
ExpectedAttributeValue user = new ExpectedAttributeValue().withValue(new AttributeValue()
.withS("user"));
userProvidedConditions.put("user", user);
Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
.mergeExpectedAttributeValueConditions(
null, userProvidedConditions, "AND");
assertEquals(merged.get("user").getValue().getS(), "user");
assertEquals(merged.size(), 1);
}
@Test
public void testMergeExpectedAttributeValueConditionsNoUserProvidedConditions() {
Map<String, ExpectedAttributeValue> internalAssertions = new HashMap<String, ExpectedAttributeValue>();
ExpectedAttributeValue internal = new ExpectedAttributeValue()
.withValue(new AttributeValue().withS("internal"));
internalAssertions.put("internal", internal);
Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
.mergeExpectedAttributeValueConditions(
internalAssertions, null, "AND");
assertEquals(merged.get("internal").getValue().getS(), "internal");
assertEquals(merged.size(), 1);
}
@Test
public void testNeedAutoGenerateAssignableKey() {
HashKeyAutoGenerated autoGenObj = new HashKeyAutoGenerated();
assertTrue(mapper.needAutoGenerateAssignableKey(HashKeyAutoGenerated.class, autoGenObj));
MockTwoValuePlusVersionClass nonAutogen = new MockTwoValuePlusVersionClass();
assertFalse(mapper.needAutoGenerateAssignableKey(MockTwoValuePlusVersionClass.class,
nonAutogen));
}
@Test
public void testBatchLoadReturnsEmptyMapWithRequestOfNoObjects() {
Map<String, List<Object>> result = mapper.batchLoad(new ArrayList<Object>());
assertEquals(result.keySet().size(), 0);
}
@Test
public void testCreateScanRequestFromExpression() {
DynamoDBScanExpression se = new DynamoDBScanExpression();
se.setConditionalOperator("lt");
Map<String, AttributeValue> esk = new HashMap<String, AttributeValue>();
se.setExclusiveStartKey(esk);
Map<String, String> ean = new HashMap<String, String>();
se.setExpressionAttributeNames(ean);
Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
se.setExpressionAttributeValues(eav);
se.setFilterExpression("testFilter");
se.setLimit(5);
Map<String, Condition> filter = new HashMap<String, Condition>();
se.setScanFilter(filter);
se.setSegment(2);
se.setTotalSegments(10);
ScanRequest sr = mapper.createScanRequestFromExpression(MockTwoValuePlusVersionClass.class,
se, config);
assertEquals(sr.getConditionalOperator(), "lt");
assertEquals(sr.getTableName(), "aws-android-sdk-dynamodbmapper-test");
assertEquals(sr.getExclusiveStartKey(), esk);
assertEquals(sr.getExpressionAttributeNames(), ean);
assertEquals(sr.getExpressionAttributeValues(), eav);
assertEquals(sr.getFilterExpression(), "testFilter");
assertEquals(sr.getLimit().intValue(), 5);
assertEquals(sr.getScanFilter(), filter);
assertEquals(sr.getSegment().intValue(), 2);
assertEquals(sr.getTotalSegments().intValue(), 10);
}
@Test
public void createParalellScanRequestsFromExpression() {
DynamoDBScanExpression se = new DynamoDBScanExpression();
se.setConditionalOperator("lt");
Map<String, AttributeValue> esk = new HashMap<String, AttributeValue>();
se.setExclusiveStartKey(esk);
Map<String, String> ean = new HashMap<String, String>();
se.setExpressionAttributeNames(ean);
Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
se.setExpressionAttributeValues(eav);
se.setFilterExpression("testFilter");
se.setLimit(5);
Map<String, Condition> filter = new HashMap<String, Condition>();
se.setScanFilter(filter);
se.setSegment(2);
se.setTotalSegments(10);
List<ScanRequest> requests = mapper.createParallelScanRequestsFromExpression(
MockTwoValuePlusVersionClass.class,
se, 2, config);
ScanRequest sr1 = requests.get(0);
assertEquals(sr1.getConditionalOperator(), "lt");
assertEquals(sr1.getTableName(), "aws-android-sdk-dynamodbmapper-test");
assertNull(sr1.getExclusiveStartKey());
assertEquals(sr1.getExpressionAttributeNames(), ean);
assertEquals(sr1.getExpressionAttributeValues(), eav);
assertEquals(sr1.getFilterExpression(), "testFilter");
assertEquals(sr1.getLimit().intValue(), 5);
assertEquals(sr1.getScanFilter(), filter);
assertEquals(sr1.getSegment().intValue(), 0);
assertEquals(sr1.getTotalSegments().intValue(), 2);
ScanRequest sr2 = requests.get(1);
assertEquals(sr2.getConditionalOperator(), "lt");
assertEquals(sr2.getTableName(), "aws-android-sdk-dynamodbmapper-test");
assertNull(sr2.getExclusiveStartKey());
assertEquals(sr2.getExpressionAttributeNames(), ean);
assertEquals(sr2.getExpressionAttributeValues(), eav);
assertEquals(sr2.getFilterExpression(), "testFilter");
assertEquals(sr2.getLimit().intValue(), 5);
assertEquals(sr2.getScanFilter(), filter);
assertEquals(sr2.getSegment().intValue(), 1);
assertEquals(sr2.getTotalSegments().intValue(), 2);
}
@Test
public void testContainsThrottlingException() {
List<FailedBatch> failedBatches = new ArrayList<FailedBatch>();
FailedBatch nonThrottle = new FailedBatch();
nonThrottle.setException(new AmazonServiceException("InvalidInput"));
failedBatches.add(nonThrottle);
assertFalse(mapper.containsThrottlingException(failedBatches));
FailedBatch throttle = new FailedBatch();
AmazonServiceException ase = new AmazonServiceException("ThrottlingException");
ase.setErrorCode("ThrottlingException");
nonThrottle.setException(ase);
failedBatches.add(throttle);
assertTrue(mapper.containsThrottlingException(failedBatches));
}
@Test
public void testSaveObjectHandler() {
MockTwoValuePlusVersionClass mockClass = new MockTwoValuePlusVersionClass("PrimaryKey",
"Value1", null);
String tableName = mapper.getTableName(MockTwoValuePlusVersionClass.class, mockClass,
config);
final Map<String, Boolean> expectedFound = new HashMap<String, Boolean>();
final String foundNullAttributeKey = "NullAttribute";
final String foundKeyKey = "Key";
SaveObjectHandler testHandler = mapper.new SaveObjectHandler(
MockTwoValuePlusVersionClass.class, mockClass,
tableName, config, mapper.getConverter(config), null) {
@Override
protected void onKeyAttributeValue(String attributeName,
AttributeValue keyAttributeValue) {
if (attributeName.equalsIgnoreCase("id")
&& keyAttributeValue.getS().equalsIgnoreCase("PrimaryKey")) {
expectedFound.put(foundKeyKey, true);
} else {
fail("Incorrect onKeyAttributeValueCalled --- received attributeName: "
+ attributeName + " with value: " + keyAttributeValue.getS());
}
}
@Override
protected void onNullNonKeyAttribute(String attributeName) {
if (attributeName.equalsIgnoreCase("SecondValue")) {
expectedFound.put(foundNullAttributeKey, true);
} else {
fail("Incorrect NullNonKeyAttribute called, received " + attributeName);
}
}
@Override
protected void executeLowLevelRequest() {
assertTrue(expectedFound.get(foundKeyKey));
assertTrue(expectedFound.get(foundNullAttributeKey));
assertTrue(getAttributeValueUpdates().get("firstValue").getValue().getS()
.equalsIgnoreCase("Value1"));
assertTrue(getAttributeValueUpdates().get("version").getValue().getN()
.equalsIgnoreCase("1"));
}
};
testHandler.execute();
}
@Test
public void testSaveObjectHandlerWithAutogeneratedKey() {
HashKeyAutoGenerated mockClass = new HashKeyAutoGenerated();
mockClass.setRangeKey("range");
mockClass.setOtherAttribute("other");
String tableName = mapper.getTableName(HashKeyAutoGenerated.class, mockClass,
config);
final Map<String, Boolean> expectedFound = new HashMap<String, Boolean>();
final String foundRangeKey = "RangeKey";
SaveObjectHandler testHandler = mapper.new SaveObjectHandler(
HashKeyAutoGenerated.class, mockClass,
tableName, config, mapper.getConverter(config), null) {
@Override
protected void onKeyAttributeValue(String attributeName,
AttributeValue keyAttributeValue) {
if (attributeName.equalsIgnoreCase("rangeKey")
&& keyAttributeValue.getS().equalsIgnoreCase("range")) {
expectedFound.put(foundRangeKey, true);
} else {
fail("Incorrect onKeyAttributeValueCalled --- received attributeName: "
+ attributeName + " with value: " + keyAttributeValue.getS());
}
}
@Override
protected void onNullNonKeyAttribute(String attributeName) {
fail("No null attributes should have been found");
}
@Override
protected void executeLowLevelRequest() {
assertNotNull(getAttributeValueUpdates().get("key").getValue().getS());
assertTrue(expectedFound.get(foundRangeKey));
assertTrue(getAttributeValueUpdates().get("otherAttribute").getValue().getS()
.equalsIgnoreCase("other"));
}
};
testHandler.execute();
}
// ----Mock test classes -----
@DynamoDBTable(tableName = "aws-android-sdk-dynamodbmapper-test")
private static final class MockTwoValuePlusVersionClass {
private String id;
private String firstValue;
private String secondValue;
private Integer version;
public MockTwoValuePlusVersionClass() {
}
public MockTwoValuePlusVersionClass(String id, String firstValue, String secondValue) {
this.id = id;
this.firstValue = firstValue;
this.secondValue = secondValue;
}
@DynamoDBHashKey
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@DynamoDBVersionAttribute
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@DynamoDBAttribute
public String getFirstValue() {
return firstValue;
}
public void setFirstValue(String value) {
this.firstValue = value;
}
public String getSecondValue() {
return secondValue;
}
public void setSecondValue(String secondValue) {
this.secondValue = secondValue;
}
}
@DynamoDBTable(tableName = "aws-android-sdk-dynamodbmapper-test-different-table")
private static final class MockDifferentTableName {
private String id;
private String firstValue;
public MockDifferentTableName(String id, String firstValue) {
this.id = id;
this.firstValue = firstValue;
}
public MockDifferentTableName() {
}
@DynamoDBHashKey
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@DynamoDBAttribute
public String getFirstValue() {
return firstValue;
}
public void setFirstValue(String firstValue) {
this.firstValue = firstValue;
}
}
private static final class FixedCapture<T> extends Capture<T> {
public static interface CapCallback<T> {
public void valueSet(T value);
}
CapCallback<T> callback;
public FixedCapture(CaptureType all, CapCallback callback) {
super(CaptureType.ALL);
this.callback = callback;
}
@Override
public void setValue(T value) {
callback.valueSet(value);
if (!hasCaptured()) {
super.setValue(value);
}
}
}
}