/*
* Copyright (c) 2010-2017 Evolveum
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.icf.dummy.resource;
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 org.apache.commons.lang.StringUtils;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.PrettyPrinter;
/**
* @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<String, Set<Object>>();
private Boolean enabled = true;
private Date validFrom = null;
private Date validTo = null;
protected DummyResource resource;
private final Set<String> auxiliaryObjectClassNames = new HashSet<>();
private BreakMode modifyBreakMode = null;
public DummyObject() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public DummyObject(String name) {
this.name = name;
}
public DummyResource getResource() {
return resource;
}
public void setResource(DummyResource resource) {
this.resource = resource;
}
public String getName() {
return name;
}
public void setName(String username) {
this.name = username;
}
public Boolean isEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
this.enabled = enabled;
}
public Date getValidFrom() {
return validFrom;
}
public void setValidFrom(Date validFrom) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
this.validFrom = validFrom;
}
public Date getValidTo() {
return validTo;
}
public void setValidTo(Date validTo) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
this.validTo = validTo;
}
public BreakMode getModifyBreakMode() {
return modifyBreakMode;
}
public void setModifyBreakMode(BreakMode modifyBreakMode) {
this.modifyBreakMode = modifyBreakMode;
}
public Set<String> getAttributeNames() {
return attributes.keySet();
}
public <T> Set<T> getAttributeValues(String attrName, Class<T> type) {
Set<Object> values = attributes.get(attrName);
return (Set)values;
}
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);
}
return values.iterator().next();
}
public String getAttributeValue(String attrName) {
return getAttributeValue(attrName,String.class);
}
public void replaceAttributeValue(String name, Object value) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
Collection<Object> values = new ArrayList<Object>(1);
values.add(value);
replaceAttributeValues(name, values);
}
public void replaceAttributeValues(String name, Collection<Object> values) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<Object>();
attributes.put(name, currentValues);
} else {
currentValues.clear();
}
currentValues.addAll(values);
checkSchema(name, values, "replace");
recordModify();
}
public void replaceAttributeValues(String name, Object... values) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<Object>();
attributes.put(name, currentValues);
} else {
currentValues.clear();
}
List<Object> valuesList = Arrays.asList(values);
currentValues.addAll(valuesList);
checkSchema(name, valuesList, "replace");
if (valuesList.isEmpty()) {
attributes.remove(name);
}
recordModify();
}
public void addAttributeValue(String name, Object value) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
Collection<Object> values = new ArrayList<Object>(1);
values.add(value);
addAttributeValues(name, values);
}
public void addAttributeValues(String name, Collection<Object> valuesToAdd) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<Object>();
attributes.put(name, currentValues);
}
for(Object valueToAdd: valuesToAdd) {
addAttributeValue(name, currentValues, valueToAdd);
}
recordModify();
}
public void addAttributeValues(String name, String... valuesToAdd) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<Object>();
attributes.put(name, currentValues);
}
for (Object valueToAdd: valuesToAdd) {
addAttributeValue(name, currentValues, valueToAdd);
}
recordModify();
}
private void addAttributeValue(String attrName, Set<Object> currentValues, Object valueToAdd) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
if (resource != null && !resource.isTolerateDuplicateValues()) {
for (Object currentValue: currentValues) {
if (currentValue.equals(valueToAdd)) {
throw new IllegalArgumentException("The value '"+valueToAdd+"' of attribute '"+attrName+"' conflicts with existing value: Attempt to add value that already exists");
}
if (resource.isCaseIgnoreValues() && (valueToAdd instanceof String)) {
if (StringUtils.equalsIgnoreCase((String)currentValue, (String)valueToAdd)) {
throw new IllegalArgumentException("The value '"+valueToAdd+"' of attribute '"+attrName+"' conflicts with existing value: Attempt to add value that already exists");
}
}
}
}
if (resource != null && resource.isMonsterization() && DummyResource.VALUE_MONSTER.equals(valueToAdd)) {
Iterator<Object> iterator = currentValues.iterator();
while (iterator.hasNext()) {
if (DummyResource.VALUE_COOKIE.equals(iterator.next())) {
iterator.remove();
}
}
}
Set<Object> valuesToCheck = new HashSet<Object>();
valuesToCheck.addAll(currentValues);
valuesToCheck.add(valueToAdd);
checkSchema(attrName, valuesToCheck, "add");
currentValues.add(valueToAdd);
}
public void removeAttributeValue(String name, Object value) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
Collection<Object> values = new ArrayList<Object>();
values.add(value);
removeAttributeValues(name, values);
}
public void removeAttributeValues(String name, Collection<Object> values) throws SchemaViolationException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
checkModifyBreak();
Set<Object> currentValues = attributes.get(name);
if (currentValues == null) {
currentValues = new HashSet<Object>();
attributes.put(name, currentValues);
}
Set<Object> valuesToCheck = new HashSet<Object>();
valuesToCheck.addAll(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()){
checkIfExist(values, currentValues);
}
while(iterator.hasNext()) {
Object currentValue = iterator.next();
boolean found = false;
for (Object value: values) {
if (resource.isCaseIgnoreValues() && currentValue instanceof String && value instanceof String) {
if (StringUtils.equalsIgnoreCase((String)currentValue, (String)value)) {
found = true;
break;
}
} else {
if (currentValue.equals(value)) {
found = true;
break;
}
}
}
if (found) {
iterator.remove();
}
}
recordModify();
}
public Set<String> getAuxiliaryObjectClassNames() {
return auxiliaryObjectClassNames;
}
public void addAuxiliaryObjectClassName(String name) {
auxiliaryObjectClassNames.add(name);
}
public void replaceAuxiliaryObjectClassNames(List<?> values) {
auxiliaryObjectClassNames.clear();
addAuxiliaryObjectClassNames(values);
}
public void deleteAuxiliaryObjectClassNames(List<?> values) {
for (Object value : values) {
auxiliaryObjectClassNames.remove(String.valueOf(value));
}
}
public void addAuxiliaryObjectClassNames(List<?> values) {
for (Object value : values) {
auxiliaryObjectClassNames.add(String.valueOf(value));
}
}
private void checkIfExist(Collection<Object> valuesToDelete, Set<Object> currentValues) throws SchemaViolationException{
for (Object valueToDelete : valuesToDelete) {
boolean found = false;
for (Object currentValue : currentValues) {
if (resource.isCaseIgnoreValues() && currentValue instanceof String && valueToDelete instanceof String) {
if (StringUtils.equalsIgnoreCase((String)currentValue, (String)valueToDelete)) {
found = true;
break;
}
} else {
if (currentValue.equals(valueToDelete)) {
found = true;
break;
}
}
}
if (!found){
throw new SchemaViolationException("no such member: " + valueToDelete + " in " + currentValues);
}
}
}
protected void checkModifyBreak() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException {
if (resource == null) {
return;
}
BreakMode modifyBreakMode = this.modifyBreakMode;
if (modifyBreakMode == null) {
modifyBreakMode = resource.getModifyBreakMode();
}
if (modifyBreakMode == BreakMode.NONE) {
// go on
} else if (modifyBreakMode == BreakMode.NETWORK) {
throw new ConnectException("Network error (simulated error)");
} else if (modifyBreakMode == BreakMode.IO) {
throw new FileNotFoundException("IO error (simulated error)");
} else if (modifyBreakMode == BreakMode.SCHEMA) {
throw new SchemaViolationException("Schema violation (simulated error)");
} else if (modifyBreakMode == BreakMode.CONFLICT) {
throw new ConflictException("Conflict (simulated error)");
} else if (modifyBreakMode == BreakMode.GENERIC) {
// The connector will react with generic exception
throw new IllegalArgumentException("Generic error (simulated error)");
} else if (modifyBreakMode == BreakMode.RUNTIME) {
// The connector will just pass this up
throw new IllegalStateException("Generic error (simulated error)");
} else if (modifyBreakMode == BreakMode.UNSUPPORTED) {
throw new UnsupportedOperationException("Not supported (simulated error)");
} else {
// This is a real error. Use this strange thing to make sure it passes up
throw new RuntimeException("Unknown schema break mode "+modifyBreakMode);
}
}
private void recordModify() {
if (resource != null) {
resource.recordModify(this);
}
}
protected void checkSchema(String attrName, Collection<Object> values, String operationName) throws SchemaViolationException {
if (resource == null || !resource.isEnforceSchema()) {
return;
}
DummyObjectClass accountObjectClass;
try {
accountObjectClass = getObjectClass();
} catch (Exception e) {
// No not enforce schema if the schema is broken (simulated)
return;
}
if (accountObjectClass == null) {
// Nothing to check
return;
}
DummyAttributeDefinition attributeDefinition = getAttributeDefinition(attrName);
if (attributeDefinition == null) {
throw new SchemaViolationException("Attribute "+attrName+" is not defined in resource schema");
}
if (attributeDefinition.isRequired() && (values == null || values.isEmpty())) {
throw new SchemaViolationException(operationName + " of required attribute "+attrName+" results in no values");
}
if (!attributeDefinition.isMulti() && values != null && values.size() > 1) {
throw new SchemaViolationException(operationName + " of single-valued attribute "+attrName+" results in "+values.size()+" values");
}
}
public DummyAttributeDefinition getAttributeDefinition(String attrName) {
DummyAttributeDefinition def = getObjectClassNoExceptions().getAttributeDefinition(attrName);
if (def != null) {
return def;
}
for (String auxClassName : getAuxiliaryObjectClassNames()) {
DummyObjectClass auxObjectClass = resource.getAuxiliaryObjectClassMap().get(auxClassName);
if (auxObjectClass == null) {
throw new IllegalStateException("Auxiliary object class " + auxClassName + " couldn't be found");
}
def = auxObjectClass.getAttributeDefinition(attrName);
if (def != null) {
return def;
}
}
return null;
}
abstract protected DummyObjectClass getObjectClass() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException;
abstract protected DummyObjectClass getObjectClassNoExceptions();
public abstract String getShortTypeName();
@Override
public String toString() {
return getClass().getSimpleName()+"(" + toStringContent() + ")";
}
protected String toStringContent() {
return "name=" + name + ", attributes=" + attributes + ", enabled=" + enabled;
}
@Override
public String debugDump() {
return debugDump(0);
}
@Override
public String debugDump(int indent) {
StringBuilder sb = new StringBuilder();
DebugUtil.indentDebugDump(sb, indent);
sb.append(getClass().getSimpleName());
sb.append(": ").append(name);
if (!auxiliaryObjectClassNames.isEmpty()) {
sb.append("\n");
DebugUtil.debugDumpWithLabelToString(sb, "Auxiliary object classes", auxiliaryObjectClassNames, indent + 1);
}
sb.append("\n");
DebugUtil.debugDumpWithLabelToString(sb, "Enabled", enabled, indent + 1);
if (validFrom != null || validTo != null) {
sb.append("\n");
DebugUtil.debugDumpLabel(sb, "Validity", indent + 1);
sb.append(" ").append(PrettyPrinter.prettyPrint(validFrom)).append(" - ").append(PrettyPrinter.prettyPrint(validTo));
}
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "Attributes", attributes, indent + 1);
extendDebugDump(sb, indent);
return sb.toString();
}
protected void extendDebugDump(StringBuilder sb, int indent) {
// Nothing to do
}
public boolean isReturnedByDefault(String attrName) {
final DummyAttributeDefinition attributeDefinition = getAttributeDefinition(attrName);
if (attributeDefinition != null) {
return attributeDefinition.isReturnedByDefault();
} else {
System.out.println("Warning: attribute " + attrName + " is not defined in " + this);
return false;
}
}
}