Ignore irrelevant parameters during start of an operation#1680
Conversation
| private Operation addParameterValues(Operation operation, Set<ParameterMetadata> predefinedParameters) { | ||
| Map<String, Object> parameters = new HashMap<>(operation.getParameters()); | ||
| parameters.putAll(ParameterConversion.toFlowableVariables(predefinedParameters, parameters)); | ||
| filterUnnecessaryParameters(predefinedParameters, parameters); |
There was a problem hiding this comment.
Can we perform this filtering in startOperation method before invocation of addServiceParameters and addParameterValues. The idea is to reduce as much as possible to add all user input parameters to the flowable context by mistake.
There was a problem hiding this comment.
I have thought about this to filter as early as possible but after debugging - I have found out that these parameters are added during the creation of the operation and the methods OperationsApiServiceImpl::addParameterValues and OperationsApiServiceImpl::addServiceParameters are actually the earliest places that the parameters of the operation are retrieved - Map<String, Object> parameters = new HashMap<>(operation.getParameters()); - thus I think the most appropriate place out of the two is the OperationsApiServiceImpl::addParameterValues method. The method just before these two, which is supposed to get the predefined parameters does not know anything about the specific parameters the user could provide in the request's body - its only purpose is to get the parameters that correspond to the type of operation - DEPLOY, BLUE_GREEN_DEPLOY, CTS_DEPLOY and etc.
| } | ||
|
|
||
| private Set<String> getNamesOfServiceParameters() { | ||
| return new HashSet<>( |
There was a problem hiding this comment.
You can use the feature since java 11 and use Set.of method to create immutable Set without nesting of different collections.
There was a problem hiding this comment.
thanks for the suggestion, fixed!
| .anyMatch(message -> message.getType() == MessageType.ERROR); | ||
| } | ||
|
|
||
| private Set<String> getNamesOfServiceParameters() { |
There was a problem hiding this comment.
Can you make this method constant located in Constants class of multiapps-controller-web package and reuse this constant also in addServiceParameters method. Making it as constant and reuse it in both methods will avoid errors in future where one variable name is added or removed in one place and forgetting to the same in other method.
There was a problem hiding this comment.
fixed - I have used an if else because the switch does not allow method calls - thus Variables.USER.getName() can not be used inside the different cases.
| .startProcess(Mockito.any(), Mockito.anyMap()); | ||
| } | ||
|
|
||
| void testStartOperationWithInvalidParametersForTheProcess() { |
| .size()); | ||
| } | ||
|
|
||
| void testStartOperationWithValidParametersForTheProcess() { |
| assertNotEquals(parameters.size(), operation.getParameters() | ||
| .size()); | ||
| assertEquals(2, operation.getParameters() | ||
| .size()); |
There was a problem hiding this comment.
When perform unit testing of methods which doesn't return any result, it's not relevant to use assertEquals because there is no returned value. That's why think about how to improve Mockito.verify assertion.
There was a problem hiding this comment.
fixed - you are completely right but I was asserting the values returned by the calls of the size() method, but it turned out that the current logic for the testing was not correct at all and I was not asserting what I needed to. Almost done with the new tests.
| operationsApiService.startOperation(mockHttpServletRequest(EXAMPLE_USER), SPACE_GUID, operation); | ||
| Mockito.verify(flowableFacade) | ||
| .startProcess(Mockito.any(), Mockito.anyMap()); | ||
| assertEquals(parameters.size(), operation.getParameters() |
There was a problem hiding this comment.
same comment as previous test assertion
There was a problem hiding this comment.
Same as the previous comment
| .withParameters(parameters); | ||
| } | ||
|
|
||
| private void filterUnnecessaryParameters(Set<ParameterMetadata> predefinedParameters, Map<String, Object> parameters) { |
There was a problem hiding this comment.
try not to use in-parameters. instead of modifying parameters, return a new Map.
There was a problem hiding this comment.
fixed, thanks for the suggestion!
| Set<String> namesOfServiceParameters = getNamesOfServiceParameters(); | ||
| Iterator<Map.Entry<String, Object>> it = parameters.entrySet() | ||
| .iterator(); | ||
| while (it.hasNext()) { | ||
| String key = it.next() | ||
| .getKey(); | ||
| if (!(namesOfPredefinedParameters.contains(key) || namesOfServiceParameters.contains(key))) { | ||
| it.remove(); | ||
| } | ||
| } |
There was a problem hiding this comment.
you can simplify this method and implicitly use the iterator.
try to use for-each with the entrySet directly and do the filtration in the for-each.
if you use a new map as I mentioned in the other comment this can be simplified
| operationsApiService.startOperation(mockHttpServletRequest(EXAMPLE_USER), SPACE_GUID, operation); | ||
| Mockito.verify(flowableFacade) | ||
| .startProcess(Mockito.any(), Mockito.anyMap()); | ||
| assertNotEquals(parameters.size(), operation.getParameters() |
There was a problem hiding this comment.
do you need this assertion as the other verifies the exact size?
| } | ||
|
|
||
| public static final Set<String> NAMES_OF_SERVICE_PARAMETERS = Set.of( | ||
| org.cloudfoundry.multiapps.controller.persistence.Constants.VARIABLE_NAME_SERVICE_ID, Variables.USER.getName(), |
There was a problem hiding this comment.
why is the full package name used?
There was a problem hiding this comment.
Because I did not want to have a static import. Should I do it this way? import static org.cloudfoundry.multiapps.controller.persistence.Constants.VARIABLE_NAME_SERVICE_ID;
| String namespace = operation.getNamespace(); | ||
| if (namespace != null) { | ||
| parameters.put(Variables.MTA_NAMESPACE.getName(), namespace); | ||
| for (String key : org.cloudfoundry.multiapps.controller.web.Constants.NAMES_OF_SERVICE_PARAMETERS) { |
There was a problem hiding this comment.
I do not understand why this for loop is needed.
What is the point of iterating and checking for values that have already been added?
There was a problem hiding this comment.
The code before that was without any loops, but since Kris has left me a comment about adding this specific set to the Constants class and trying to use it also in the method OperationsApiServiceImpl::addServiceParameters - I was able to come up with only this approach in order to make the method tied to the constant set and use it
| Set<String> allowedParameters = new HashSet<>(); | ||
| Map<String, Object> filteredParameters = new LinkedHashMap<>(); | ||
|
|
||
| for (ParameterMetadata parameterMetadata : predefinedParameters) { |
There was a problem hiding this comment.
instead of for loop, can you create allowedParameters from the predefinedParameters
|
|
||
| private Map<String, Object> filterUnnecessaryParameters(Set<ParameterMetadata> predefinedParameters, Map<String, Object> parameters) { | ||
| Set<String> allowedParameters = new HashSet<>(); | ||
| Map<String, Object> filteredParameters = new LinkedHashMap<>(); |
There was a problem hiding this comment.
why LinkedHashMap?
There was a problem hiding this comment.
Because it was easier for while debugging in terms of the ordering and I thought it would not be a problem to have it like that - but I will revert it back to the regular hashMap. Thanks, fixed!
| for (Map.Entry<String, Object> entry : parameters.entrySet()) { | ||
| if (allowedParameters.contains(entry.getKey())) { | ||
| filteredParameters.put(entry.getKey(), entry.getValue()); | ||
| } | ||
| } | ||
| return filteredParameters; |
There was a problem hiding this comment.
you can use the java stream API to perform the filtering
| for (ParameterMetadata parameterMetadata : predefinedParameters) { | ||
| allowedParameters.add(parameterMetadata.getId()); | ||
| } | ||
| allowedParameters.addAll(org.cloudfoundry.multiapps.controller.web.Constants.NAMES_OF_SERVICE_PARAMETERS); |
There was a problem hiding this comment.
why full package name needed?
| @@ -277,8 +291,26 @@ private Operation addServiceParameters(Operation operation, String spaceGuid, St | |||
| private Operation addParameterValues(Operation operation, Set<ParameterMetadata> predefinedParameters) { | |||
| Map<String, Object> parameters = new HashMap<>(operation.getParameters()); | |||
There was a problem hiding this comment.
can you make the whole filtration here?
| if (key.equals(Constants.VARIABLE_NAME_SERVICE_ID)) { | ||
| parameters.put(key, processDefinitionKey); | ||
| } else if (key.equals(Variables.USER.getName())) { | ||
| parameters.put(key, user); | ||
| } else if (key.equals(Variables.USER_GUID.getName())) { | ||
| parameters.put(key, userGuid); | ||
| } else if (key.equals(Variables.SPACE_NAME.getName())) { | ||
| parameters.put(key, space.getName()); | ||
| } else if (key.equals(Variables.SPACE_GUID.getName())) { | ||
| parameters.put(key, spaceGuid); | ||
| } else if (key.equals(Variables.ORGANIZATION_NAME.getName())) { | ||
| parameters.put(key, organization.getName()); | ||
| } else if (key.equals(Variables.ORGANIZATION_GUID.getName())) { | ||
| parameters.put(key, organization.getGuid() | ||
| .toString()); | ||
| } else if (key.equals(Variables.TIMESTAMP.getName())) { | ||
| parameters.put(key, DateTimeFormatter.ofPattern("yyyyMMddHHmmss") | ||
| .format(ZonedDateTime.now())); | ||
| } else if (key.equals(Variables.MTA_NAMESPACE.getName())) { | ||
| String namespace = operation.getNamespace(); | ||
| if (namespace != null) { | ||
| parameters.put(key, namespace); | ||
| } |
There was a problem hiding this comment.
Too much ifs, let's return it in previous way.
| Set<String> allowedParameters = new HashSet<>(); | ||
| Map<String, Object> filteredParameters = new LinkedHashMap<>(); | ||
|
|
||
| for (ParameterMetadata parameterMetadata : predefinedParameters) { | ||
| allowedParameters.add(parameterMetadata.getId()); | ||
| } | ||
| allowedParameters.addAll(org.cloudfoundry.multiapps.controller.web.Constants.NAMES_OF_SERVICE_PARAMETERS); |
There was a problem hiding this comment.
Extract collecting of allowedParameters in a new method.
| private Map<String, Object> filterUnnecessaryParameters(Set<ParameterMetadata> predefinedParameters, Map<String, Object> parameters) { | ||
| Map<String, Object> filteredParameters = new HashMap<>(); | ||
| Set<String> allowedParameters = getAllowedParameters(predefinedParameters); | ||
|
|
||
| parameters.entrySet() | ||
| .stream() | ||
| .filter(entry -> allowedParameters.contains(entry.getKey())) | ||
| .forEach(entry -> filteredParameters.put(entry.getKey(), entry.getValue())); | ||
| return filteredParameters; | ||
| } |
There was a problem hiding this comment.
**private Map<String, Object> filterUnnecessaryParameters(
Set predefinedParameters,
Map<String, Object> parameters) {
Set<String> allowedParameters = getAllowedParameters(predefinedParameters);
return parameters.entrySet()
.stream()
.filter(entry -> allowedParameters.contains(entry.getKey()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(a, b) -> b, // keep the latest value if a duplicate key appears
HashMap::new // create a mutable map
));
}**
IvanBorislavovDimitrov
left a comment
There was a problem hiding this comment.
do not forget to squash commits
LMCROSSITXSADEPLOY-3120 Fixes regarding service parameters and the unit tests LMCROSSITXSADEPLOY-3120 Some overall fixes regarding formatting LMCROSSITXSADEPLOY-3120 Fix for the stream LMCROSSITXSADEPLOY-3120
a0601b5 to
36a2936
Compare
31eba39
into
cloudfoundry:master
Jira Item: LMCROSSITXSADEPLOY-3120