/* * 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.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.UUID; import com.evolveum.midpoint.util.exception.SystemException; import org.apache.commons.lang.StringUtils; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; /** * Resource for use with dummy ICF connector. * * This is a simple Java object that pretends to be a resource. It has accounts and * account schema. It has operations to manipulate accounts, execute scripts and so on * almost like a real resource. The purpose is to simulate a real resource with a very * little overhead. * * The resource is a singleton, therefore the resource instance can be shared by * the connector and the test code. The usual story is like this: * * 1) test class fetches first instance of the resource (getInstance). This will cause * loading of the resource class in the test (parent) classloader. * * 2) test class configures the connector (e.g. schema) usually by calling the populateWithDefaultSchema() method. * * 3) test class initializes IDM. This will cause connector initialization. The connector will fetch * the instance of dummy resource. As it was loaded by the parent classloader, it will get the same instance * as the test class. * * 4) test class invokes IDM operation. That will invoke connector and change the resource. * * 5) test class will access resource directly to see if the operation went OK. * * The dummy resource is a separate package (JAR) from the dummy connector. Connector has its own * classloader. If the resource would be the same package as connector, it will get loaded by the * connector classloader regardless whether it is already loaded by the parent classloader. * * @author Radovan Semancik * */ public class DummyResource implements DebugDumpable { private static final Trace LOGGER = TraceManager.getTrace(DummyResource.class); public static final String ATTRIBUTE_CONNECTOR_TO_STRING = "connectorToString"; public static final String ATTRIBUTE_CONNECTOR_STATIC_VAL = "connectorStaticVal"; public static final String ATTRIBUTE_CONNECTOR_CONFIGURATION_TO_STRING = "connectorConfigurationToString"; private String instanceName; private Map<String,DummyObject> allObjects; private Map<String,DummyAccount> accounts; private Map<String,DummyGroup> groups; private Map<String,DummyPrivilege> privileges; private Map<String,DummyOrg> orgs; private List<ScriptHistoryEntry> scriptHistory; private DummyObjectClass accountObjectClass; private DummyObjectClass groupObjectClass; private DummyObjectClass privilegeObjectClass; private final Map<String,DummyObjectClass> auxiliaryObjectClassMap = new HashMap<>(); private DummySyncStyle syncStyle; private List<DummyDelta> deltas; private int latestSyncToken; private boolean tolerateDuplicateValues = false; private boolean generateDefaultValues = false; private boolean enforceUniqueName = true; private boolean enforceSchema = true; private boolean caseIgnoreId = false; private boolean caseIgnoreValues = false; private int connectionCount = 0; private int groupMembersReadCount = 0; private Collection<String> forbiddenNames; /** * There is a monster that loves to eat cookies. * If value "monster" is added to an attribute that * contain the "cookie" value, the monster will * eat that cookie. Then is goes to sleep. If more * cookies are added then the monster will not * eat them. * MID-3727 */ private boolean monsterization = false; public static final String VALUE_MONSTER = "monster"; public static final String VALUE_COOKIE = "cookie"; private BreakMode schemaBreakMode = BreakMode.NONE; private BreakMode getBreakMode = BreakMode.NONE; private BreakMode addBreakMode = BreakMode.NONE; private BreakMode modifyBreakMode = BreakMode.NONE; private BreakMode deleteBreakMode = BreakMode.NONE; private boolean blockOperations = false; private boolean generateAccountDescriptionOnCreate = false; // simulates volatile behavior (on create) private boolean generateAccountDescriptionOnUpdate = false; // simulates volatile behavior (on update) private boolean disableNameHintChecks = false; // Following two properties are just copied from the connector // configuration and can be checked later. They are otherwise // completely useless. private String uselessString; private String uselessGuardedString; private static Map<String, DummyResource> instances = new HashMap<String, DummyResource>(); DummyResource() { allObjects = Collections.synchronizedMap(new LinkedHashMap<String,DummyObject>()); accounts = Collections.synchronizedMap(new LinkedHashMap<String, DummyAccount>()); groups = Collections.synchronizedMap(new LinkedHashMap<String, DummyGroup>()); privileges = Collections.synchronizedMap(new LinkedHashMap<String, DummyPrivilege>()); orgs = Collections.synchronizedMap(new LinkedHashMap<String, DummyOrg>()); scriptHistory = new ArrayList<ScriptHistoryEntry>(); accountObjectClass = new DummyObjectClass(); groupObjectClass = new DummyObjectClass(); privilegeObjectClass = new DummyObjectClass(); syncStyle = DummySyncStyle.NONE; deltas = Collections.synchronizedList(new ArrayList<DummyDelta>()); latestSyncToken = 0; } /** * Clears everything, just like the resouce was just created. */ public void reset() { allObjects.clear(); accounts.clear(); groups.clear(); privileges.clear(); orgs.clear(); scriptHistory.clear(); accountObjectClass = new DummyObjectClass(); groupObjectClass = new DummyObjectClass(); privilegeObjectClass = new DummyObjectClass(); syncStyle = DummySyncStyle.NONE; deltas.clear(); latestSyncToken = 0; resetBreakMode(); } public static DummyResource getInstance() { return getInstance(null); } public static DummyResource getInstance(String instanceName) { DummyResource instance = instances.get(instanceName); if (instance == null) { instance = new DummyResource(); instance.setInstanceName(instanceName); instances.put(instanceName, instance); } return instance; } public String getInstanceName() { return instanceName; } public void setInstanceName(String instanceName) { this.instanceName = instanceName; } public boolean isTolerateDuplicateValues() { return tolerateDuplicateValues; } public void setTolerateDuplicateValues(boolean tolerateDuplicateValues) { this.tolerateDuplicateValues = tolerateDuplicateValues; } public boolean isGenerateDefaultValues() { return generateDefaultValues; } public void setGenerateDefaultValues(boolean generateDefaultValues) { this.generateDefaultValues = generateDefaultValues; } public boolean isEnforceUniqueName() { return enforceUniqueName; } public void setEnforceUniqueName(boolean enforceUniqueName) { this.enforceUniqueName = enforceUniqueName; } public boolean isEnforceSchema() { return enforceSchema; } public void setEnforceSchema(boolean enforceSchema) { this.enforceSchema = enforceSchema; } public BreakMode getSchemaBreakMode() { return schemaBreakMode; } public void setSchemaBreakMode(BreakMode schemaBreakMode) { this.schemaBreakMode = schemaBreakMode; } public BreakMode getAddBreakMode() { return addBreakMode; } public void setAddBreakMode(BreakMode addBreakMode) { this.addBreakMode = addBreakMode; } public BreakMode getGetBreakMode() { return getBreakMode; } public void setGetBreakMode(BreakMode getBreakMode) { this.getBreakMode = getBreakMode; } public BreakMode getModifyBreakMode() { return modifyBreakMode; } public void setModifyBreakMode(BreakMode modifyBreakMode) { this.modifyBreakMode = modifyBreakMode; } public BreakMode getDeleteBreakMode() { return deleteBreakMode; } public void setDeleteBreakMode(BreakMode deleteBreakMode) { this.deleteBreakMode = deleteBreakMode; } public void setBreakMode(BreakMode breakMode) { this.schemaBreakMode = breakMode; this.addBreakMode = breakMode; this.getBreakMode = breakMode; this.modifyBreakMode = breakMode; this.deleteBreakMode = breakMode; } public void resetBreakMode() { setBreakMode(BreakMode.NONE); } public boolean isBlockOperations() { return blockOperations; } public void setBlockOperations(boolean blockOperations) { this.blockOperations = blockOperations; } public String getUselessString() { return uselessString; } public void setUselessString(String uselessString) { this.uselessString = uselessString; } public String getUselessGuardedString() { return uselessGuardedString; } public void setUselessGuardedString(String uselessGuardedString) { this.uselessGuardedString = uselessGuardedString; } public boolean isCaseIgnoreId() { return caseIgnoreId; } public void setCaseIgnoreId(boolean caseIgnoreId) { this.caseIgnoreId = caseIgnoreId; } public boolean isCaseIgnoreValues() { return caseIgnoreValues; } public void setCaseIgnoreValues(boolean caseIgnoreValues) { this.caseIgnoreValues = caseIgnoreValues; } public boolean isGenerateAccountDescriptionOnCreate() { return generateAccountDescriptionOnCreate; } public void setGenerateAccountDescriptionOnCreate(boolean generateAccountDescriptionOnCreate) { this.generateAccountDescriptionOnCreate = generateAccountDescriptionOnCreate; } public boolean isGenerateAccountDescriptionOnUpdate() { return generateAccountDescriptionOnUpdate; } public void setGenerateAccountDescriptionOnUpdate(boolean generateAccountDescriptionOnUpdate) { this.generateAccountDescriptionOnUpdate = generateAccountDescriptionOnUpdate; } public boolean isDisableNameHintChecks() { return disableNameHintChecks; } public void setDisableNameHintChecks(boolean disableNameHintChecks) { this.disableNameHintChecks = disableNameHintChecks; } public Collection<String> getForbiddenNames() { return forbiddenNames; } public void setForbiddenNames(Collection<String> forbiddenNames) { this.forbiddenNames = forbiddenNames; } public boolean isMonsterization() { return monsterization; } public void setMonsterization(boolean monsterization) { this.monsterization = monsterization; } public int getConnectionCount() { return connectionCount; } public synchronized void connect() { connectionCount++; } public synchronized void disconnect() { connectionCount--; } public void assertNoConnections() { assert connectionCount == 0 : "Dummy resource: "+connectionCount+" connections still open"; } public int getGroupMembersReadCount() { return groupMembersReadCount; } public void setGroupMembersReadCount(int groupMembersReadCount) { this.groupMembersReadCount = groupMembersReadCount; } public void recordGroupMembersReadCount() { groupMembersReadCount++; traceOperation("groupMembersRead", groupMembersReadCount); } public DummyObjectClass getAccountObjectClass() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { breakIt(schemaBreakMode, "schema"); return accountObjectClass; } public DummyObjectClass getAccountObjectClassNoExceptions() { return accountObjectClass; } public DummyObjectClass getGroupObjectClass() { return groupObjectClass; } public DummyObjectClass getPrivilegeObjectClass() { return privilegeObjectClass; } public Map<String,DummyObjectClass> getAuxiliaryObjectClassMap() { return auxiliaryObjectClassMap; } public void addAuxiliaryObjectClass(String name, DummyObjectClass objectClass) { auxiliaryObjectClassMap.put(name, objectClass); } public Collection<DummyAccount> listAccounts() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(getBreakMode, "get"); return accounts.values(); } private <T extends DummyObject> T getObjectByName(Map<String,T> map, String name, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { if (!enforceUniqueName) { throw new IllegalStateException("Attempt to search object by name while resource is in non-unique name mode"); } if (checkBreak) { breakIt(getBreakMode, "get"); } return map.get(normalize(name)); } public DummyAccount getAccountByUsername(String username) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(accounts, username, true); } public DummyAccount getAccountByUsername(String username, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(accounts, username, checkBreak); } public DummyGroup getGroupByName(String name) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(groups, name, true); } public DummyGroup getGroupByName(String name, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(groups, name, checkBreak); } public DummyPrivilege getPrivilegeByName(String name) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(privileges, name, true); } public DummyPrivilege getPrivilegeByName(String name, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(privileges, name, checkBreak); } public DummyOrg getOrgByName(String name) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(orgs, name, true); } public DummyOrg getOrgByName(String name, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectByName(orgs, name, checkBreak); } private <T extends DummyObject> T getObjectById(Class<T> expectedClass, String id, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { if (checkBreak) { breakIt(getBreakMode, "get"); } DummyObject dummyObject = allObjects.get(id); if (dummyObject == null) { return null; } if (!expectedClass.isInstance(dummyObject)) { throw new IllegalStateException("Arrrr! Wanted "+expectedClass+" with ID "+id+" but got "+dummyObject+" instead"); } return (T)dummyObject; } public DummyAccount getAccountById(String id) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyAccount.class, id, true); } public DummyAccount getAccountById(String id, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyAccount.class, id, checkBreak); } public DummyGroup getGroupById(String id) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyGroup.class, id, true); } public DummyGroup getGroupById(String id, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyGroup.class, id, checkBreak); } public DummyPrivilege getPrivilegeById(String id) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyPrivilege.class, id, true); } public DummyPrivilege getPrivilegeById(String id, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyPrivilege.class, id, checkBreak); } public DummyOrg getOrgById(String id) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyOrg.class, id, true); } public DummyOrg getOrgById(String id, boolean checkBreak) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { return getObjectById(DummyOrg.class, id, checkBreak); } public Collection<DummyGroup> listGroups() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(getBreakMode, "get"); return groups.values(); } public Collection<DummyPrivilege> listPrivileges() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(getBreakMode, "get"); return privileges.values(); } public Collection<DummyOrg> listOrgs() throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(getBreakMode, "get"); return orgs.values(); } private synchronized <T extends DummyObject> String addObject(Map<String,T> map, T newObject) throws ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(addBreakMode, "add"); Class<? extends DummyObject> type = newObject.getClass(); String normalName = normalize(newObject.getName()); if (normalName != null && forbiddenNames != null && forbiddenNames.contains(normalName)) { throw new ObjectAlreadyExistsException(normalName + " is forbidden to use as an object name"); } String newId = UUID.randomUUID().toString(); newObject.setId(newId); if (allObjects.containsKey(newId)) { throw new IllegalStateException("The hell is frozen over. The impossible has happened. ID "+newId+" already exists ("+ type.getSimpleName()+" with identifier "+normalName+")"); } //this is "resource-generated" attribute (used to simulate resource which generate by default attributes which we need to sync) if (generateDefaultValues){ // int internalId = allObjects.size(); newObject.addAttributeValue(DummyAccount.ATTR_INTERNAL_ID, new Random().nextInt()); } String mapKey; if (enforceUniqueName) { mapKey = normalName; } else { mapKey = newId; } if (map.containsKey(mapKey)) { throw new ObjectAlreadyExistsException(type.getSimpleName()+" with name '"+normalName+"' already exists"); } newObject.setResource(this); map.put(mapKey, newObject); allObjects.put(newId, newObject); if (syncStyle != DummySyncStyle.NONE) { int syncToken = nextSyncToken(); DummyDelta delta = new DummyDelta(syncToken, type, newId, newObject.getName(), DummyDeltaType.ADD); deltas.add(delta); } return newObject.getName(); } private synchronized <T extends DummyObject> void deleteObjectByName(Class<T> type, Map<String,T> map, String name) throws ObjectDoesNotExistException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(deleteBreakMode, "delete"); String normalName = normalize(name); T existingObject; if (!enforceUniqueName) { throw new IllegalStateException("Whoops! got into deleteObjectByName without enforceUniqueName"); } if (map.containsKey(normalName)) { existingObject = map.get(normalName); map.remove(normalName); allObjects.remove(existingObject.getId()); } else { throw new ObjectDoesNotExistException(type.getSimpleName()+" with name '"+normalName+"' does not exist"); } if (syncStyle != DummySyncStyle.NONE) { int syncToken = nextSyncToken(); DummyDelta delta = new DummyDelta(syncToken, type, existingObject.getId(), name, DummyDeltaType.DELETE); deltas.add(delta); } } public void deleteAccountById(String id) throws ConnectException, FileNotFoundException, ObjectDoesNotExistException, SchemaViolationException, ConflictException { deleteObjectById(DummyAccount.class, accounts, id); } public void deleteGroupById(String id) throws ConnectException, FileNotFoundException, ObjectDoesNotExistException, SchemaViolationException, ConflictException { deleteObjectById(DummyGroup.class, groups, id); } public void deletePrivilegeById(String id) throws ConnectException, FileNotFoundException, ObjectDoesNotExistException, SchemaViolationException, ConflictException { deleteObjectById(DummyPrivilege.class, privileges, id); } public void deleteOrgById(String id) throws ConnectException, FileNotFoundException, ObjectDoesNotExistException, SchemaViolationException, ConflictException { deleteObjectById(DummyOrg.class, orgs, id); } private synchronized <T extends DummyObject> void deleteObjectById(Class<T> type, Map<String,T> map, String id) throws ObjectDoesNotExistException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(deleteBreakMode, "delete"); DummyObject object = allObjects.get(id); if (object == null) { throw new ObjectDoesNotExistException(type.getSimpleName()+" with id '"+id+"' does not exist"); } if (!type.isInstance(object)) { throw new IllegalStateException("Arrrr! Wanted "+type+" with ID "+id+" but got "+object+" instead"); } T existingObject = (T)object; String normalName = normalize(object.getName()); allObjects.remove(id); String mapKey; if (enforceUniqueName) { mapKey = normalName; } else { mapKey = id; } if (map.containsKey(mapKey)) { map.remove(mapKey); } else { throw new ObjectDoesNotExistException(type.getSimpleName()+" with name '"+normalName+"' does not exist"); } if (syncStyle != DummySyncStyle.NONE) { int syncToken = nextSyncToken(); DummyDelta delta = new DummyDelta(syncToken, type, id, object.getName(), DummyDeltaType.DELETE); deltas.add(delta); } } private <T extends DummyObject> void renameObject(Class<T> type, Map<String,T> map, String id, String oldName, String newName) throws ObjectDoesNotExistException, ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { checkBlockOperations(); breakIt(modifyBreakMode, "modify"); T existingObject; if (enforceUniqueName) { String normalOldName = normalize(oldName); String normalNewName = normalize(newName); existingObject = map.get(normalOldName); if (existingObject == null) { throw new ObjectDoesNotExistException("Cannot rename, "+type.getSimpleName()+" with username '"+normalOldName+"' does not exist"); } if (map.containsKey(normalNewName)) { throw new ObjectAlreadyExistsException("Cannot rename, "+type.getSimpleName()+" with username '"+normalNewName+"' already exists"); } map.put(normalNewName, existingObject); map.remove(normalOldName); } else { existingObject = (T) allObjects.get(id); } existingObject.setName(newName); if (existingObject instanceof DummyAccount) { changeDescriptionIfNeeded((DummyAccount) existingObject); } } public String addAccount(DummyAccount newAccount) throws ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { if (generateAccountDescriptionOnCreate && newAccount.getAttributeValue(DummyAccount.ATTR_DESCRIPTION_NAME) == null) { newAccount.addAttributeValue(DummyAccount.ATTR_DESCRIPTION_NAME, "Description of " + newAccount.getName()); } return addObject(accounts, newAccount); } public void deleteAccountByName(String id) throws ObjectDoesNotExistException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { deleteObjectByName(DummyAccount.class, accounts, id); } public void renameAccount(String id, String oldUsername, String newUsername) throws ObjectDoesNotExistException, ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, SchemaViolationException, ConflictException { renameObject(DummyAccount.class, accounts, id, oldUsername, newUsername); for (DummyGroup group : groups.values()) { if (group.containsMember(oldUsername)) { group.removeMember(oldUsername); group.addMember(newUsername); } } } public void changeDescriptionIfNeeded(DummyAccount account) throws SchemaViolationException, ConflictException { if (generateAccountDescriptionOnCreate) { try { account.replaceAttributeValue(DummyAccount.ATTR_DESCRIPTION_NAME, "Updated description of " + account.getName()); } catch (SchemaViolationException|ConnectException|FileNotFoundException e) { throw new SystemException("Couldn't replace the 'description' attribute value", e); } } } public String addGroup(DummyGroup newGroup) throws ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, SchemaViolationException, ConflictException { return addObject(groups, newGroup); } public void deleteGroupByName(String id) throws ObjectDoesNotExistException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { deleteObjectByName(DummyGroup.class, groups, id); } public void renameGroup(String id, String oldName, String newName) throws ObjectDoesNotExistException, ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { renameObject(DummyGroup.class, groups, id, oldName, newName); } public String addPrivilege(DummyPrivilege newGroup) throws ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, SchemaViolationException, ConflictException { return addObject(privileges, newGroup); } public void deletePrivilegeByName(String id) throws ObjectDoesNotExistException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { deleteObjectByName(DummyPrivilege.class, privileges, id); } public void renamePrivilege(String id, String oldName, String newName) throws ObjectDoesNotExistException, ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { renameObject(DummyPrivilege.class, privileges, id, oldName, newName); } public String addOrg(DummyOrg newGroup) throws ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, SchemaViolationException, ConflictException { return addObject(orgs, newGroup); } public void deleteOrgByName(String id) throws ObjectDoesNotExistException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { deleteObjectByName(DummyOrg.class, orgs, id); } public void renameOrg(String id, String oldName, String newName) throws ObjectDoesNotExistException, ObjectAlreadyExistsException, ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { renameObject(DummyOrg.class, orgs, id, oldName, newName); } void recordModify(DummyObject dObject) { if (syncStyle != DummySyncStyle.NONE) { int syncToken = nextSyncToken(); DummyDelta delta = new DummyDelta(syncToken, dObject.getClass(), dObject.getId(), dObject.getName(), DummyDeltaType.MODIFY); deltas.add(delta); } } /** * Returns script history ordered chronologically (oldest first). * @return script history */ public List<ScriptHistoryEntry> getScriptHistory() { return scriptHistory; } /** * Clears the script history. */ public void purgeScriptHistory() { scriptHistory.clear(); } /** * Pretend to run script on the resource. * The script is actually not executed, it is only recorded in the script history * and can be fetched by getScriptHistory(). * * @param scriptCode code of the script */ public void runScript(String language, String scriptCode, Map<String, Object> params) { scriptHistory.add(new ScriptHistoryEntry(language, scriptCode, params)); } /** * Populates the resource with some kind of "default" schema. This is a schema that should suit * majority of basic test cases. */ public void populateWithDefaultSchema() { accountObjectClass.clear(); accountObjectClass.addAttributeDefinition(DummyAccount.ATTR_FULLNAME_NAME, String.class, true, false); accountObjectClass.addAttributeDefinition(DummyAccount.ATTR_INTERNAL_ID, String.class, false, false); accountObjectClass.addAttributeDefinition(DummyAccount.ATTR_DESCRIPTION_NAME, String.class, false, false); accountObjectClass.addAttributeDefinition(DummyAccount.ATTR_INTERESTS_NAME, String.class, false, true); accountObjectClass.addAttributeDefinition(DummyAccount.ATTR_PRIVILEGES_NAME, String.class, false, true); groupObjectClass.clear(); groupObjectClass.addAttributeDefinition(DummyGroup.ATTR_MEMBERS_NAME, String.class, false, true); privilegeObjectClass.clear(); } public DummySyncStyle getSyncStyle() { return syncStyle; } public void setSyncStyle(DummySyncStyle syncStyle) { this.syncStyle = syncStyle; } private synchronized int nextSyncToken() { return ++latestSyncToken; } public int getLatestSyncToken() { return latestSyncToken; } private String normalize(String id) { if (caseIgnoreId) { return StringUtils.lowerCase(id); } else { return id; } } public List<DummyDelta> getDeltasSince(int syncToken) { List<DummyDelta> result = new ArrayList<DummyDelta>(); for (DummyDelta delta: deltas) { if (delta.getSyncToken() > syncToken) { result.add(delta); } } return result; } void breakIt(BreakMode breakMode, String operation) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { if (breakMode == BreakMode.NONE) { return; } else if (breakMode == BreakMode.NETWORK) { throw new ConnectException("Network error (simulated error)"); } else if (breakMode == BreakMode.IO) { throw new FileNotFoundException("IO error (simulated error)"); } else if (breakMode == BreakMode.SCHEMA) { throw new SchemaViolationException("Schema violation (simulated error)"); } else if (breakMode == BreakMode.CONFLICT) { throw new ConflictException("Conflict (simulated error)"); } else if (breakMode == BreakMode.GENERIC) { // The connector will react with generic exception throw new IllegalArgumentException("Generic error (simulated error)"); } else if (breakMode == BreakMode.RUNTIME) { // The connector will just pass this up throw new IllegalStateException("Generic error (simulated error)"); } else if (breakMode == 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 "+operation+" break mode "+getBreakMode); } } private synchronized void checkBlockOperations() { if (blockOperations) { try { LOGGER.info("Thread {} blocked", Thread.currentThread().getName()); this.wait(); LOGGER.info("Thread {} unblocked", Thread.currentThread().getName()); } catch (InterruptedException e) { LOGGER.debug("Wait interrupted", e); } } } public synchronized void unblock() { LOGGER.info("Unblocking"); this.notify(); } public synchronized void unblockAll() { LOGGER.info("Unblocking all"); this.notifyAll(); } private void traceOperation(String opName, long counter) { LOGGER.info("MONITOR dummy '{}' {} ({})", instanceName, opName, counter); if (LOGGER.isDebugEnabled()) { StackTraceElement[] fullStack = Thread.currentThread().getStackTrace(); String immediateClass = null; String immediateMethod = null; StringBuilder sb = new StringBuilder(); for (StackTraceElement stackElement: fullStack) { if (stackElement.getClassName().equals(DummyResource.class.getName()) || stackElement.getClassName().equals(Thread.class.getName())) { // skip our own calls continue; } if (immediateClass == null) { immediateClass = stackElement.getClassName(); immediateMethod = stackElement.getMethodName(); } sb.append(stackElement.toString()); sb.append("\n"); } LOGGER.debug("MONITOR dummy '{}' {} ({}): {} {}", new Object[]{instanceName, opName, counter, immediateClass, immediateMethod}); LOGGER.trace("MONITOR dummy '{}' {} ({}):\n{}", new Object[]{instanceName, opName, counter, sb}); } } @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(toString()); DebugUtil.indentDebugDump(sb, indent); sb.append("\nAccounts:"); for (Entry<String, DummyAccount> entry: accounts.entrySet()) { sb.append("\n "); sb.append(entry.getKey()); sb.append(": "); sb.append(entry.getValue()); } sb.append("\nGroups:"); for (Entry<String, DummyGroup> entry: groups.entrySet()) { sb.append("\n "); sb.append(entry.getKey()); sb.append(": "); sb.append(entry.getValue()); } sb.append("\nPrivileges:"); for (Entry<String, DummyPrivilege> entry: privileges.entrySet()) { sb.append("\n "); sb.append(entry.getKey()); sb.append(": "); sb.append(entry.getValue()); } sb.append("\nOrgs:"); for (Entry<String, DummyOrg> entry: orgs.entrySet()) { sb.append("\n "); sb.append(entry.getKey()); sb.append(": "); sb.append(entry.getValue()); } sb.append("\nDeltas:"); for (DummyDelta delta: deltas) { sb.append("\n "); sb.append(delta); } sb.append("\nLatest token:").append(latestSyncToken); return sb.toString(); } @Override public String toString() { return "DummyResource("+instanceName+": "+accounts.size()+" accounts, "+groups.size()+" groups, "+privileges.size()+" privileges, "+orgs.size()+" orgs)"; } }