/* * Copyright (c) 2010-2016 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.midpoint.model.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.xml.namespace.QName; import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.api.TaskService; import com.evolveum.midpoint.prism.ConsistencyCheckScope; import com.evolveum.midpoint.prism.crypto.Protector; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.impl.controller.ModelUtils; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.query.ObjectPaging; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher; import com.evolveum.midpoint.provisioning.api.GenericConnectorException; import com.evolveum.midpoint.provisioning.api.ResourceEventDescription; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.cache.RepositoryCache; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ConsistencyViolationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectShadowChangeDescriptionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; /** * Simple version of model service exposing CRUD-like operations. This is common facade for webservice and REST services. * It takes care of all the "details" of externalized obejcts such as applying correct definitions and so on. * * @author Radovan Semancik * */ @Component public class ModelCrudService { String CLASS_NAME_WITH_DOT = ModelCrudService.class.getName() + "."; String ADD_OBJECT = CLASS_NAME_WITH_DOT + "addObject"; String MODIFY_OBJECT = CLASS_NAME_WITH_DOT + "modifyObject"; String DELETE_OBJECT = CLASS_NAME_WITH_DOT + "deleteObject"; private static final Trace LOGGER = TraceManager.getTrace(ModelCrudService.class); @Autowired(required = true) ModelService modelService; @Autowired TaskService taskService; @Autowired(required = true) PrismContext prismContext; @Autowired(required = true) @Qualifier("cacheRepositoryService") RepositoryService repository; @Autowired(required = true) private Protector protector; @Autowired(required = true) private ChangeNotificationDispatcher dispatcher; public <T extends ObjectType> PrismObject<T> getObject(Class<T> clazz, String oid, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { return modelService.getObject(clazz, oid, options, task, parentResult); } public <T extends ObjectType> List<PrismObject<T>> searchObjects(Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { return modelService.searchObjects(type, query, options, task, parentResult); } public void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescription, OperationResult parentResult, Task task) throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException{ String oldShadowOid = changeDescription.getOldShadowOid(); ResourceEventDescription eventDescription = new ResourceEventDescription(); PrismObject<ShadowType> oldShadow = null; LOGGER.trace("resolving old object"); if (!StringUtils.isEmpty(oldShadowOid)){ oldShadow = getObject(ShadowType.class, oldShadowOid, SelectorOptions.createCollection(GetOperationOptions.createDoNotDiscovery()), task, parentResult); eventDescription.setOldShadow(oldShadow); LOGGER.trace("old object resolved to: {}", oldShadow.debugDump()); } else{ LOGGER.trace("Old shadow null"); } PrismObject<ShadowType> currentShadow = null; ShadowType currentShadowType = changeDescription.getCurrentShadow(); LOGGER.trace("resolving current shadow"); if (currentShadowType != null){ prismContext.adopt(currentShadowType); currentShadow = currentShadowType.asPrismObject(); LOGGER.trace("current shadow resolved to {}", currentShadow.debugDump()); } eventDescription.setCurrentShadow(currentShadow); ObjectDeltaType deltaType = changeDescription.getObjectDelta(); ObjectDelta delta = null; PrismObject<ShadowType> shadowToAdd = null; if (deltaType != null){ delta = ObjectDelta.createEmptyDelta(ShadowType.class, deltaType.getOid(), prismContext, ChangeType.toChangeType(deltaType.getChangeType())); if (delta.getChangeType() == ChangeType.ADD) { // LOGGER.trace("determined ADD change "); if (deltaType.getObjectToAdd() == null){ LOGGER.trace("No object to add specified. Check your delta. Add delta must contain object to add"); throw new IllegalArgumentException("No object to add specified. Check your delta. Add delta must contain object to add"); // return handleTaskResult(task); } Object objToAdd = deltaType.getObjectToAdd(); if (!(objToAdd instanceof ShadowType)){ LOGGER.trace("Wrong object specified in change description. Expected on the the shadow type, but got " + objToAdd.getClass().getSimpleName()); throw new IllegalArgumentException("Wrong object specified in change description. Expected on the the shadow type, but got " + objToAdd.getClass().getSimpleName()); // return handleTaskResult(task); } prismContext.adopt((ShadowType)objToAdd); shadowToAdd = ((ShadowType) objToAdd).asPrismObject(); LOGGER.trace("object to add: {}", shadowToAdd.debugDump()); delta.setObjectToAdd(shadowToAdd); } else { Collection<? extends ItemDelta> modifications = DeltaConvertor.toModifications(deltaType.getItemDelta(), prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class)); delta.getModifications().addAll(modifications); } } Collection<ObjectDelta<? extends ObjectType>> deltas = new ArrayList<ObjectDelta<? extends ObjectType>>(); deltas.add(delta); Utils.encrypt(deltas, protector, null, parentResult); eventDescription.setDelta(delta); eventDescription.setSourceChannel(changeDescription.getChannel()); dispatcher.notifyEvent(eventDescription, task, parentResult); parentResult.computeStatus(); task.setResult(parentResult); } /** * <p> * Add new object. * </p> * <p> * The OID provided in the input message may be empty. In that case the OID * will be assigned by the implementation of this method and it will be * provided as return value. * </p> * <p> * This operation should fail if such object already exists (if object with * the provided OID already exists). * </p> * <p> * The operation may fail if provided OID is in an unusable format for the * storage. Generating own OIDs and providing them to this method is not * recommended for normal operation. * </p> * <p> * Should be atomic. Should not allow creation of two objects with the same * OID (even if created in parallel). * </p> * <p> * The operation may fail if the object to be created does not conform to * the underlying schema of the storage system or the schema enforced by the * implementation. * </p> * * @param object * object to create * @param parentResult * parent OperationResult (in/out) * @return OID assigned to the created object * @throws ObjectAlreadyExistsException * object with specified identifiers already exists, cannot add * @throws ObjectNotFoundException * object required to complete the operation was not found (e.g. * appropriate connector or resource definition) * @throws SchemaException * error dealing with resource schema, e.g. created object does * not conform to schema * @throws ExpressionEvaluationException * evaluation of expression associated with the object has failed * @throws CommunicationException * @throws ConfigurationException * @throws PolicyViolationException * Policy violation was detected during processing of the object * @throws IllegalArgumentException * wrong OID format, etc. * @throws SystemException * unknown error from underlying layers or other unexpected * state */ public <T extends ObjectType> String addObject(PrismObject<T> object, ModelExecuteOptions options, Task task, OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { Validate.notNull(object, "Object must not be null."); Validate.notNull(parentResult, "Result type must not be null."); object.checkConsistence(); T objectType = object.asObjectable(); prismContext.adopt(objectType); OperationResult result = parentResult.createSubresult(ADD_OBJECT); result.addParams(new String[] { "object" }, object); Utils.resolveReferences(object, repository, false, false, EvaluationTimeType.IMPORT, true, prismContext, result); String oid; RepositoryCache.enter(); try { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Entering addObject with {}", object); LOGGER.trace(object.debugDump()); } if (options == null) { if (StringUtils.isNotEmpty(objectType.getVersion())) { options = ModelExecuteOptions.createOverwrite(); } } ObjectDelta<T> objectDelta = ObjectDelta.createAddDelta(object); Collection<ObjectDelta<? extends ObjectType>> deltas = MiscSchemaUtil.createCollection(objectDelta); modelService.executeChanges(deltas, options, task, result); oid = objectDelta.getOid(); result.computeStatus(); result.cleanupResult(); } catch (ExpressionEvaluationException | SchemaException | ObjectNotFoundException | ObjectAlreadyExistsException | SecurityViolationException | ConfigurationException | RuntimeException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } finally { RepositoryCache.exit(); } return oid; } /** * <p> * Deletes object with specified OID. * </p> * <p> * Must fail if object with specified OID does not exists. Should be atomic. * </p> * * @param oid * OID of object to delete * @param parentResult * parent OperationResult (in/out) * @throws ObjectNotFoundException * specified object does not exist * @throws IllegalArgumentException * wrong OID format, described change is not applicable * @throws ConsistencyViolationException * sub-operation failed, cannot delete objects as its deletion * would lead to inconsistent state * @throws CommunicationException * @throws ConfigurationException * @throws PolicyViolationException * Policy violation was detected during processing of the object * @throws SystemException * unknown error from underlying layers or other unexpected * state */ public <T extends ObjectType> void deleteObject(Class<T> clazz, String oid, ModelExecuteOptions options, Task task, OperationResult parentResult) throws ObjectNotFoundException, ConsistencyViolationException, CommunicationException, SchemaException, ConfigurationException, PolicyViolationException, SecurityViolationException { Validate.notNull(clazz, "Class must not be null."); Validate.notEmpty(oid, "Oid must not be null or empty."); Validate.notNull(parentResult, "Result type must not be null."); OperationResult result = parentResult.createSubresult(DELETE_OBJECT); result.addParams(new String[] { "oid" }, oid); RepositoryCache.enter(); try { ObjectDelta<T> objectDelta = new ObjectDelta<T>(clazz, ChangeType.DELETE, prismContext); objectDelta.setOid(oid); LOGGER.trace("Deleting object with oid {}.", new Object[] { oid }); Collection<ObjectDelta<? extends ObjectType>> deltas = MiscSchemaUtil.createCollection(objectDelta); modelService.executeChanges(deltas, options, task, result); result.recordSuccess(); } catch (ObjectNotFoundException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (CommunicationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (SecurityViolationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (RuntimeException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (ObjectAlreadyExistsException ex) { ModelUtils.recordFatalError(result, ex); throw new SystemException(ex.getMessage(), ex); } catch (ExpressionEvaluationException ex) { ModelUtils.recordFatalError(result, ex); throw new SystemException(ex.getMessage(), ex); } finally { RepositoryCache.exit(); } } /** * <p> * Modifies object using relative change description. * </p> * <p> * Must fail if user with provided OID does not exists. Must fail if any of * the described changes cannot be applied. Should be atomic. * </p> * <p> * If two or more modify operations are executed in parallel, the operations * should be merged. In case that the operations are in conflict (e.g. one * operation adding a value and the other removing the same value), the * result is not deterministic. * </p> * <p> * The operation may fail if the modified object does not conform to the * underlying schema of the storage system or the schema enforced by the * implementation. * </p> * * @param parentResult * parent OperationResult (in/out) * @throws ObjectNotFoundException * specified object does not exist * @throws SchemaException * resulting object would violate the schema * @throws ExpressionEvaluationException * evaluation of expression associated with the object has failed * @throws CommunicationException * @throws ObjectAlreadyExistsException * If the account or another "secondary" object already exists and cannot be created * @throws PolicyViolationException * Policy violation was detected during processing of the object * @throws IllegalArgumentException * wrong OID format, described change is not applicable * @throws SystemException * unknown error from underlying layers or other unexpected * state */ public <T extends ObjectType> void modifyObject(Class<T> type, String oid, Collection<? extends ItemDelta> modifications, ModelExecuteOptions options, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, ObjectAlreadyExistsException, PolicyViolationException, SecurityViolationException { Validate.notNull(modifications, "Object modification must not be null."); Validate.notEmpty(oid, "Change oid must not be null or empty."); Validate.notNull(parentResult, "Result type must not be null."); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Modifying object with oid {}", oid); LOGGER.trace(DebugUtil.debugDump(modifications)); } if (modifications.isEmpty()) { LOGGER.warn("Calling modifyObject with empty modificaiton set"); return; } ItemDelta.checkConsistence(modifications, ConsistencyCheckScope.THOROUGH); // TODO: check definitions, but tolerate missing definitions in <attributes> OperationResult result = parentResult.createSubresult(MODIFY_OBJECT); result.addCollectionOfSerializablesAsParam("modifications", modifications); RepositoryCache.enter(); try { ObjectDelta<T> objectDelta = (ObjectDelta<T>) ObjectDelta.createModifyDelta(oid, modifications, type, prismContext); Collection<ObjectDelta<? extends ObjectType>> deltas = MiscSchemaUtil.createCollection(objectDelta); modelService.executeChanges(deltas, options, task, result); result.computeStatus(); } catch (ExpressionEvaluationException ex) { LOGGER.error("model.modifyObject failed: {}", ex.getMessage(), ex); result.recordFatalError(ex); throw ex; } catch (ObjectNotFoundException ex) { LOGGER.error("model.modifyObject failed: {}", ex.getMessage(), ex); result.recordFatalError(ex); throw ex; } catch (SchemaException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (ConfigurationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (SecurityViolationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (RuntimeException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } finally { RepositoryCache.exit(); } } public PrismObject<UserType> findShadowOwner(String accountOid, Task task, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException { return modelService.findShadowOwner(accountOid, task, parentResult); } public List<PrismObject<? extends ShadowType>> listResourceObjects(String resourceOid, QName objectClass, ObjectPaging paging, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { return modelService.listResourceObjects(resourceOid, objectClass, paging, task, parentResult); } public void importFromResource(String resourceOid, QName objectClass, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { modelService.importFromResource(resourceOid, objectClass, task, parentResult); } public OperationResult testResource(String resourceOid, Task task) throws ObjectNotFoundException { return modelService.testResource(resourceOid, task); } //TASK AREA public boolean suspendTasks(Collection<String> taskOids, long waitForStop, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { return taskService.suspendTasks(taskOids, waitForStop, parentResult); } public void suspendAndDeleteTasks(Collection<String> taskOids, long waitForStop, boolean alsoSubtasks, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { taskService.suspendAndDeleteTasks(taskOids, waitForStop, alsoSubtasks, parentResult); } public void resumeTasks(Collection<String> taskOids, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { taskService.resumeTasks(taskOids, parentResult); } public void scheduleTasksNow(Collection<String> taskOids, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { taskService.scheduleTasksNow(taskOids, parentResult); } public PrismObject<TaskType> getTaskByIdentifier(String identifier, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException { return taskService.getTaskByIdentifier(identifier, options, parentResult); } public boolean deactivateServiceThreads(long timeToWait, OperationResult parentResult) throws SchemaException, SecurityViolationException { return taskService.deactivateServiceThreads(timeToWait, parentResult); } public void reactivateServiceThreads(OperationResult parentResult) throws SchemaException, SecurityViolationException { taskService.reactivateServiceThreads(parentResult); } public boolean getServiceThreadsActivationState() { return taskService.getServiceThreadsActivationState(); } public void stopSchedulers(Collection<String> nodeIdentifiers, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { taskService.stopSchedulers(nodeIdentifiers, parentResult); } public boolean stopSchedulersAndTasks(Collection<String> nodeIdentifiers, long waitTime, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { return taskService.stopSchedulersAndTasks(nodeIdentifiers, waitTime, parentResult); } public void startSchedulers(Collection<String> nodeIdentifiers, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { taskService.startSchedulers(nodeIdentifiers, parentResult); } public void synchronizeTasks(OperationResult parentResult) throws SchemaException, SecurityViolationException { taskService.synchronizeTasks(parentResult); } public List<String> getAllTaskCategories() { return taskService.getAllTaskCategories(); } public String getHandlerUriForCategory(String category) { return taskService.getHandlerUriForCategory(category); } }