-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: put should return previous value instead of Optional #2595
Changes from all commits
204ac34
d492ff6
73688b8
e392924
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,16 @@ | |
import java.util.Optional; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowCleanupResult; | ||
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult; | ||
|
||
@SuppressWarnings("rawtypes") | ||
public class DefaultManagedDependentResourceContext implements ManagedDependentResourceContext { | ||
private static final Logger log = | ||
LoggerFactory.getLogger(DefaultManagedDependentResourceContext.class); | ||
public static final Object RECONCILE_RESULT_KEY = new Object(); | ||
public static final Object CLEANUP_RESULT_KEY = new Object(); | ||
private final ConcurrentHashMap attributes = new ConcurrentHashMap(); | ||
|
@@ -22,10 +27,26 @@ public <T> Optional<T> get(Object key, Class<T> expectedType) { | |
@Override | ||
@SuppressWarnings("unchecked") | ||
public <T> T put(Object key, T value) { | ||
Object previous; | ||
if (value == null) { | ||
return (T) Optional.ofNullable(attributes.remove(key)); | ||
return (T) attributes.remove(key); | ||
} else { | ||
previous = attributes.put(key, value); | ||
} | ||
|
||
if (previous != null && !previous.getClass().isAssignableFrom(value.getClass())) { | ||
logWarning("Previous value (" + previous + | ||
") for key (" + key + | ||
") was not of type " + value.getClass() + | ||
". This might indicate an issue in your code. If not, use put(" + key + | ||
", null) first to remove the previous value."); | ||
} | ||
return (T) Optional.ofNullable(attributes.put(key, value)); | ||
return (T) previous; | ||
} | ||
|
||
// only for testing purposes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems this is not only used in testing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The whole purpose of this method is to be overridden in the test so that we can check that the logging really happens without having to set up a complex logging fixture just for that purpose. |
||
void logWarning(String message) { | ||
log.warn(message); | ||
} | ||
|
||
@Override | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package io.javaoperatorsdk.operator.api.reconciler.dependent.managed; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import io.javaoperatorsdk.operator.processing.dependent.workflow.WorkflowReconcileResult; | ||
|
||
import static io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext.RECONCILE_RESULT_KEY; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
class DefaultManagedDependentResourceContextTest { | ||
|
||
private final ManagedDependentResourceContext context = | ||
new DefaultManagedDependentResourceContext(); | ||
|
||
@Test | ||
void getWhenEmpty() { | ||
Optional<String> actual = context.get("key", String.class); | ||
assertThat(actual).isEmpty(); | ||
} | ||
|
||
@Test | ||
void get() { | ||
context.put("key", "value"); | ||
Optional<String> actual = context.get("key", String.class); | ||
assertThat(actual).contains("value"); | ||
} | ||
|
||
@Test | ||
void putNewValueOverwrites() { | ||
context.put("key", "value"); | ||
context.put("key", "valueB"); | ||
Optional<String> actual = context.get("key", String.class); | ||
assertThat(actual).contains("valueB"); | ||
} | ||
|
||
@Test | ||
void putNewValueReturnsPriorValue() { | ||
final var prior = "value"; | ||
context.put("key", prior); | ||
String actual = context.put("key", "valueB"); | ||
assertThat(actual).isEqualTo(prior); | ||
} | ||
|
||
@Test | ||
void putNewValueThrowsExceptionIfTypesDiffer() { | ||
// to check that we properly log things without setting up a complex fixture | ||
final String[] messages = new String[1]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't need to be an array. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless I am mistaken, it does. Simply using a final String doesn't work since there is no way for Java to check that it is always assigned and therefore complains that you're assigning a value to a final variable in the overridden method. Not using a final String won't work either because this is effectively a capture so the variable needs to be final so the only way that I know of to do this is to use a final array and change the element. |
||
var context = new DefaultManagedDependentResourceContext() { | ||
@Override | ||
void logWarning(String message) { | ||
messages[0] = message; | ||
} | ||
}; | ||
final var prior = "value"; | ||
final var key = "key"; | ||
context.put(key, prior); | ||
context.put(key, 10); | ||
assertThat(messages[0]).contains(key).contains(prior).contains("put(" + key + ", null)"); | ||
} | ||
|
||
@Test | ||
void putNullRemoves() { | ||
context.put("key", "value"); | ||
context.put("key", null); | ||
Optional<String> actual = context.get("key", String.class); | ||
assertThat(actual).isEmpty(); | ||
} | ||
|
||
|
||
@Test | ||
void putNullReturnsPriorValue() { | ||
context.put("key", "value"); | ||
String actual = context.put("key", null); | ||
assertThat(actual).contains("value"); | ||
} | ||
|
||
@Test | ||
void getMandatory() { | ||
context.put("key", "value"); | ||
String actual = context.getMandatory("key", String.class); | ||
assertThat(actual).isEqualTo("value"); | ||
} | ||
|
||
@Test | ||
void getMandatoryWhenEmpty() { | ||
assertThatThrownBy(() -> { | ||
context.getMandatory("key", String.class); | ||
}).isInstanceOf(IllegalStateException.class) | ||
.hasMessage( | ||
"Mandatory attribute (key: key, type: java.lang.String) is missing or not of the expected type"); | ||
} | ||
|
||
@Test | ||
void getWorkflowReconcileResult() { | ||
WorkflowReconcileResult result = | ||
new WorkflowReconcileResult(List.of(), List.of(), Map.of(), Map.of()); | ||
context.put(RECONCILE_RESULT_KEY, result); | ||
Optional<WorkflowReconcileResult> actual = context.getWorkflowReconcileResult(); | ||
assertThat(actual).containsSame(result); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, there should be a reset method. This seems unintuitive to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, or simply a
remove
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was debating that, indeed. Not sure how many people would actually need this so decided against it (people would usually put stuff in the context and get it back instead of removing them so I think this is an edge case).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is true, that the scope of this is the actual reconciliation. So maybe we don't even need remove at all. But if we do would be nice to have it explicit.