Skip to content

Commit

Permalink
Make dummy objects thread safe
Browse files Browse the repository at this point in the history
This is an attempt to enable DummyGroup objects to participate
in multi-threaded tests.

It also incorporates approximate fixing of null values handling
in DummyObject: As the thread safety was ensured using
ConcurrentHashMap (and corresponding Set implementation),
the null values can no longer be stored in attributes.
This is not a problem, as they do not belong there anyway.

So we filter out such values. Unfortunately, we do not do
that 100% consistently, so there are places (like sync delta creation)
where such values leak.

(cherry picked from commit 729d699)
(cherry picked from commit b47e629)
  • Loading branch information
mederly committed May 5, 2021
1 parent ff65c7c commit f586522
Showing 1 changed file with 84 additions and 79 deletions.
Expand Up @@ -8,40 +8,35 @@

import java.io.FileNotFoundException;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;

import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.PrettyPrinter;

import static java.util.Collections.*;

/**
* @author Radovan Semancik
* TODO Treat null attribute values more consistently.
*
* @author Radovan Semancik
*/
public abstract class DummyObject implements DebugDumpable {

private String id;
// private int internalId = -1;
private String name;
private Map<String,Set<Object>> attributes = new HashMap<>();
private final Map<String,Set<Object>> attributes = new ConcurrentHashMap<>();
private Boolean enabled = true;
private Date validFrom = null;
private Date validTo = null;
private String lastModifier;
protected DummyResource resource;

private final Set<String> auxiliaryObjectClassNames = new HashSet<>();
private final Set<String> auxiliaryObjectClassNames = ConcurrentHashMap.newKeySet();

private BreakMode modifyBreakMode = null;

Expand Down Expand Up @@ -80,33 +75,36 @@ public Boolean isEnabled() {
return enabled;
}

public void setEnabled(Boolean enabled) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
public void setEnabled(Boolean enabled)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
this.enabled = enabled;
recordModify("_ENABLED", null, null, Arrays.asList(enabled));
recordModify("_ENABLED", null, null, singletonList(enabled));
}

public Date getValidFrom() {
return validFrom;
}

public void setValidFrom(Date validFrom) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
public void setValidFrom(Date validFrom)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
this.validFrom = validFrom;
recordModify("_VALID_FROM", null, null, Arrays.asList(validFrom));
recordModify("_VALID_FROM", null, null, singletonList(validFrom));
}

public Date getValidTo() {
return validTo;
}

public void setValidTo(Date validTo) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
public void setValidTo(Date validTo)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
this.validTo = validTo;
recordModify("_VALID_TO", null, null, Arrays.asList(validTo));
recordModify("_VALID_TO", null, null, singletonList(validTo));
}

public String getLastModifier() {
Expand All @@ -130,17 +128,17 @@ public Set<String> getAttributeNames() {
}

public <T> Set<T> getAttributeValues(String attrName, Class<T> type) {
Set<Object> values = attributes.get(attrName);
return (Set)values;
//noinspection unchecked
return (Set<T>) attributes.get(attrName);
}

public <T> T getAttributeValue(String attrName, Class<T> type) {
Set<T> values = getAttributeValues(attrName, type);
if (values == null || values.isEmpty()) {
return null;
}
if (values.size()>1) {
throw new IllegalArgumentException("Attempt to fetch single value from a multi-valued attribute "+attrName);
if (values.size() > 1) {
throw new IllegalArgumentException("Attempt to fetch single value from a multi-valued attribute " + attrName);
}
return values.iterator().next();
}
Expand All @@ -149,83 +147,95 @@ public String getAttributeValue(String attrName) {
return getAttributeValue(attrName,String.class);
}

public void replaceAttributeValue(String name, Object value) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
Collection<Object> values = new ArrayList<>(1);
values.add(value);
replaceAttributeValues(name, values);
public void replaceAttributeValue(String name, Object value)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
replaceAttributeValues(name, createCollection(value));
}

public void replaceAttributeValues(String name, Collection<Object> values) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
private Set<Object> createCollection(Object value) {
return value != null ? singleton(value) : emptySet();
}

public void replaceAttributeValues(String name, Collection<Object> values)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<>();
attributes.put(name, currentValues);
} else {
currentValues.clear();
}
currentValues.addAll(values);

Set<Object> currentValues = getOrCreateAttributeValueSet(name);
currentValues.clear();

addNonNullValues(currentValues, values);
checkSchema(name, values, "replace");
recordModify(name, null, null, values);
}

public void replaceAttributeValues(String name, Object... values) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
private void addNonNullValues(Set<Object> currentValues, Collection<Object> values) {
for (Object value : values) {
if (value != null) {
currentValues.add(value);
}
}
}

private Set<Object> getOrCreateAttributeValueSet(String name) {
return attributes.computeIfAbsent(name, k -> ConcurrentHashMap.newKeySet());
}

public void replaceAttributeValues(String name, Object... values)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<>();
attributes.put(name, currentValues);
} else {
currentValues.clear();
}

Set<Object> currentValues = getOrCreateAttributeValueSet(name);
currentValues.clear();

List<Object> valuesList = Arrays.asList(values);
currentValues.addAll(valuesList);
addNonNullValues(currentValues, valuesList);
checkSchema(name, valuesList, "replace");

// Why isn't the same code in the "collection" version of this method (above)?
// Maybe to check null-attribute handling of UCF layer, see TestDummy.test129NullAttributeValue.
if (valuesList.isEmpty()) {
attributes.remove(name);
}
recordModify(name, null, null, Arrays.asList(values));
recordModify(name, null, null, valuesList);
}

public void addAttributeValue(String name, Object value) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
Collection<Object> values = new ArrayList<>(1);
values.add(value);
addAttributeValues(name, values);
public void addAttributeValue(String name, Object value)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
addAttributeValues(name, createCollection(value));
}

public <T> void addAttributeValues(String name, Collection<T> valuesToAdd) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
public <T> void addAttributeValues(String name, Collection<T> valuesToAdd)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<>();
attributes.put(name, currentValues);
}
for(T valueToAdd: valuesToAdd) {
Set<Object> currentValues = getOrCreateAttributeValueSet(name);
for (T valueToAdd: valuesToAdd) {
addAttributeValue(name, currentValues, valueToAdd);
}
recordModify(name, valuesToAdd, null, null);
}

public void addAttributeValues(String name, String... valuesToAdd) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
public void addAttributeValues(String name, String... valuesToAdd)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<>();
attributes.put(name, currentValues);
}
Set<Object> currentValues = getOrCreateAttributeValueSet(name);
for (Object valueToAdd: valuesToAdd) {
addAttributeValue(name, currentValues, valueToAdd);
}
recordModify(name, Arrays.asList(valuesToAdd), null, null);
}

private void addAttributeValue(String attrName, Set<Object> currentValues, Object valueToAdd) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
private void addAttributeValue(String attrName, Set<Object> currentValues, Object valueToAdd)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();

if (valueToAdd == null) {
return; // Concurrent hash map does not allow null values.
}
if (resource != null && !resource.isTolerateDuplicateValues()) {
for (Object currentValue: currentValues) {
if (currentValue.equals(valueToAdd)) {
Expand Down Expand Up @@ -257,34 +267,29 @@ private void addAttributeValue(String attrName, Set<Object> currentValues, Objec
currentValues.add(valueToAdd);
}

public void removeAttributeValue(String name, Object value) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
Collection<Object> values = new ArrayList<>();
values.add(value);
removeAttributeValues(name, values);
public void removeAttributeValue(String name, Object value)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
removeAttributeValues(name, createCollection(value));
}

public <T> void removeAttributeValues(String name, Collection<T> values) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
public <T> void removeAttributeValues(String name, Collection<T> values)
throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException, InterruptedException {
checkModifyBreak();
delayOperation();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<>();
attributes.put(name, currentValues);
}
Set<Object> currentValues = getOrCreateAttributeValueSet(name);

Set<Object> valuesToCheck = new HashSet<>();
valuesToCheck.addAll(currentValues);
Set<Object> valuesToCheck = new HashSet<>(currentValues);
valuesToCheck.removeAll(values);
checkSchema(name, valuesToCheck, "remove");

Iterator<Object> iterator = currentValues.iterator();
boolean foundMember = false;

if (name.equals(DummyGroup.ATTR_MEMBERS_NAME) && !resource.isTolerateDuplicateValues()){
if (name.equals(DummyGroup.ATTR_MEMBERS_NAME) && !resource.isTolerateDuplicateValues()) {
checkIfExist(values, currentValues);
}

while(iterator.hasNext()) {
while (iterator.hasNext()) {
Object currentValue = iterator.next();
boolean found = false;
for (Object value: values) {
Expand Down Expand Up @@ -351,7 +356,7 @@ private <T> void checkIfExist(Collection<T> valuesToDelete, Set<Object> currentV
}
}

if (!found){
if (!found) {
throw new SchemaViolationException("no such member: " + valueToDelete + " in " + currentValues);
}
}
Expand Down

0 comments on commit f586522

Please sign in to comment.