com.evolveum.midpoint.model.impl.expr.MidpointFunctionsImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.impl.expr.MidpointFunctionsImpl.java

Source

/*
 * Copyright (c) 2010-2015 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.expr;

import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.model.api.context.ModelContext;
import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision;
import com.evolveum.midpoint.model.api.expr.MidpointFunctions;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.match.DefaultMatchingRule;
import com.evolveum.midpoint.prism.parser.XPathHolder;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.AndFilter;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.RefFilter;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.ResultHandler;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.Holder;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
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.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.*;
import com.evolveum.midpoint.xml.ns._public.model.model_context_3.LensContextType;

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.api.ModelService;
import com.evolveum.midpoint.model.api.PolicyViolationException;
import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext;
import com.evolveum.midpoint.model.impl.ModelObjectResolver;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensFocusContext;
import com.evolveum.midpoint.model.impl.lens.LensProjectionContext;
import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;

import java.util.*;

import javax.xml.namespace.QName;

/**
 * @author semancik
 *
 */
@Component
public class MidpointFunctionsImpl implements MidpointFunctions {

    private static final Trace LOGGER = TraceManager.getTrace(MidpointFunctionsImpl.class);

    @Autowired(required = true)
    private PrismContext prismContext;

    @Autowired(required = true)
    private ModelService modelService;

    @Autowired(required = true)
    private ModelObjectResolver modelObjectResolver;

    @Autowired(required = true)
    @Qualifier("cacheRepositoryService")
    private RepositoryService repositoryService;

    @Autowired
    private ProvisioningService provisioningService;

    @Autowired(required = true)
    private transient Protector protector;

    public String hello(String name) {
        return "Hello " + name;
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    @Override
    public List<String> toList(String... s) {
        return Arrays.asList(s);
    }

    /**
     * Returns a list of user's managers. Formally, for each Org O which this user has (any) relation to,
     * all managers of O are added to the result.
     *
     * Some customizations are probably necessary here, e.g. filter out project managers (keep only line managers),
     * or defining who is a manager of a user who is itself a manager in its org.unit. (A parent org unit manager,
     * perhaps.)
     *
     * @param user
     * @return list of oids of the respective managers
     * @throws SchemaException
     * @throws ObjectNotFoundException
     */
    @Override
    public Collection<String> getManagersOids(UserType user) throws SchemaException, ObjectNotFoundException {
        Set<String> retval = new HashSet<String>();
        for (UserType u : getManagers(user)) {
            retval.add(u.getOid());
        }
        return retval;
    }

    @Override
    public Collection<String> getManagersOidsExceptUser(UserType user)
            throws SchemaException, ObjectNotFoundException {
        Set<String> retval = new HashSet<String>();
        for (UserType u : getManagers(user)) {
            if (!u.getOid().equals(user.getOid())) {
                retval.add(u.getOid());
            }
        }
        return retval;
    }

    @Override
    public Collection<UserType> getManagers(UserType user) throws SchemaException, ObjectNotFoundException {
        return getManagers(user, null, false);
    }

    @Override
    public Collection<UserType> getManagersByOrgType(UserType user, String orgType)
            throws SchemaException, ObjectNotFoundException {
        return getManagers(user, orgType, false);
    }

    @Override
    public Collection<UserType> getManagers(UserType user, String orgType, boolean allowSelf)
            throws SchemaException, ObjectNotFoundException {
        Set<UserType> retval = new HashSet<UserType>();
        Collection<String> orgOids = getOrgUnits(user, null);
        while (!orgOids.isEmpty()) {
            LOGGER.trace("orgOids: {}", orgOids);
            Collection<OrgType> thisLevelOrgs = new ArrayList<OrgType>();
            for (String orgOid : orgOids) {
                if (orgType != null) {
                    OrgType org = getOrgByOid(orgOid);
                    if (!org.getOrgType().contains(orgType)) {
                        continue;
                    } else {
                        thisLevelOrgs.add(org);
                    }
                }
                Collection<UserType> managersOfOrg = getManagersOfOrg(orgOid);
                for (UserType managerOfOrg : managersOfOrg) {
                    if (allowSelf || !managerOfOrg.getOid().equals(user.getOid())) {
                        retval.add(managerOfOrg);
                    }
                }
            }
            LOGGER.trace("retval: {}", retval);
            if (!retval.isEmpty()) {
                return retval;
            }
            Collection<String> nextLevelOids = new ArrayList<String>();
            if (orgType == null) {
                for (String orgOid : orgOids) {
                    OrgType org = getOrgByOid(orgOid);
                    thisLevelOrgs.add(org);
                }
            }
            for (OrgType org : thisLevelOrgs) {
                for (ObjectReferenceType parentOrgRef : org.getParentOrgRef()) {
                    if (!nextLevelOids.contains(parentOrgRef.getOid())) {
                        nextLevelOids.add(parentOrgRef.getOid());
                    }
                }
            }
            LOGGER.trace("nextLevelOids: {}", nextLevelOids);
            orgOids = nextLevelOids;
        }
        return retval;
    }

    @Override
    public UserType getUserByOid(String oid) throws ObjectNotFoundException, SchemaException {
        return repositoryService.getObject(UserType.class, oid, null, new OperationResult("getUserByOid"))
                .asObjectable();
    }

    // todo here we could select "functional" org.units in order to filter out e.g. project managers from the list of managers
    // however, the syntax of orgType attribute is not standardized
    @Override
    public Collection<String> getOrgUnits(UserType user) {
        Set<String> retval = new HashSet<String>();
        if (user == null) {
            return retval;
        }
        for (ObjectReferenceType orgRef : user.getParentOrgRef()) {
            retval.add(orgRef.getOid());
        }
        return retval;
    }

    @Override
    public Collection<String> getOrgUnits(UserType user, QName relation) {
        Set<String> retval = new HashSet<>();
        if (user == null) {
            return retval;
        }
        for (ObjectReferenceType orgRef : user.getParentOrgRef()) {
            if (QNameUtil.match(relation, orgRef.getRelation())) {
                retval.add(orgRef.getOid());
            }
        }
        return retval;
    }

    @Override
    public OrgType getOrgByOid(String oid) throws SchemaException {
        try {
            return repositoryService.getObject(OrgType.class, oid, null, new OperationResult("getOrgByOid"))
                    .asObjectable();
        } catch (ObjectNotFoundException e) {
            return null;
        }
    }

    @Override
    public OrgType getOrgByName(String name) throws SchemaException {
        PolyString polyName = new PolyString(name);
        ObjectQuery q = ObjectQueryUtil.createNameQuery(polyName, prismContext);
        List<PrismObject<OrgType>> result = repositoryService.searchObjects(OrgType.class, q, null,
                new OperationResult("getOrgByName"));
        if (result.isEmpty()) {
            return null;
        }
        if (result.size() > 1) {
            throw new IllegalStateException("More than one organizational unit with the name '" + name
                    + "' (there are " + result.size() + " of them)");
        }
        return result.get(0).asObjectable();
    }

    @Override
    public OrgType getParentOrgByOrgType(ObjectType object, String orgType)
            throws SchemaException, SecurityViolationException {
        Collection<OrgType> parentOrgs = getParentOrgs(object, PrismConstants.Q_ANY, orgType);
        if (parentOrgs.isEmpty()) {
            return null;
        }
        if (parentOrgs.size() > 1) {
            throw new IllegalArgumentException("Expected that there will be just one parent org of type " + orgType
                    + " for " + object + ", but there were " + parentOrgs.size());
        }
        return parentOrgs.iterator().next();
    }

    @Override
    public Collection<OrgType> getParentOrgsByRelation(ObjectType object, QName relation)
            throws SchemaException, SecurityViolationException {
        return getParentOrgs(object, relation, null);
    }

    @Override
    public Collection<OrgType> getParentOrgsByRelation(ObjectType object, String relation)
            throws SchemaException, SecurityViolationException {
        return getParentOrgs(object, relation, null);
    }

    @Override
    public Collection<OrgType> getParentOrgs(ObjectType object) throws SchemaException, SecurityViolationException {
        return getParentOrgs(object, PrismConstants.Q_ANY, null);
    }

    @Override
    public Collection<OrgType> getParentOrgs(ObjectType object, String relation, String orgType)
            throws SchemaException, SecurityViolationException {
        return getParentOrgs(object, new QName(null, relation), orgType);
    }

    @Override
    public Collection<OrgType> getParentOrgs(ObjectType object, QName relation, String orgType)
            throws SchemaException, SecurityViolationException {
        List<ObjectReferenceType> parentOrgRefs = object.getParentOrgRef();
        List<OrgType> parentOrgs = new ArrayList<OrgType>(parentOrgRefs.size());
        for (ObjectReferenceType parentOrgRef : parentOrgRefs) {
            if (relation == null) {
                if (parentOrgRef.getRelation() != null) {
                    continue;
                }
            } else if (!relation.equals(PrismConstants.Q_ANY)) {
                if (!QNameUtil.match(parentOrgRef.getRelation(), relation)) {
                    continue;
                }
            }
            OrgType parentOrg;
            try {
                parentOrg = getObject(OrgType.class, parentOrgRef.getOid());
            } catch (ObjectNotFoundException e) {
                LOGGER.warn("Org " + parentOrgRef.getOid() + " specified in parentOrgRef in " + object
                        + " was not found: " + e.getMessage(), e);
                // but do not rethrow, just skip this
                continue;
            } catch (CommunicationException | ConfigurationException e) {
                // This should not happen.
                throw new SystemException(e.getMessage(), e);
            }
            if (orgType == null || parentOrg.getOrgType().contains(orgType)) {
                parentOrgs.add(parentOrg);
            }
        }
        return parentOrgs;
    }

    @Override
    public Collection<UserType> getManagersOfOrg(String orgOid) throws SchemaException {
        Set<UserType> retval = new HashSet<UserType>();
        OperationResult result = new OperationResult("getManagerOfOrg");

        PrismObjectDefinition<UserType> userDef = prismContext.getSchemaRegistry()
                .findObjectDefinitionByCompileTimeClass(UserType.class);
        PrismReferenceDefinition parentOrgRefDef = userDef.findReferenceDefinition(ObjectType.F_PARENT_ORG_REF);
        PrismReference parentOrgRef = parentOrgRefDef.instantiate();
        PrismReferenceValue parentOrgRefVal = new PrismReferenceValue(orgOid, OrgType.COMPLEX_TYPE);
        parentOrgRefVal.setRelation(SchemaConstants.ORG_MANAGER);
        parentOrgRef.add(parentOrgRefVal);
        ObjectQuery objectQuery = ObjectQuery.createObjectQuery(
                RefFilter.createReferenceEqual(new ItemPath(ObjectType.F_PARENT_ORG_REF), parentOrgRef));

        //        ObjectQuery objectQuery = ObjectQuery.createObjectQuery(OrgFilter.createOrg(orgOid, OrgFilter.Scope.ONE_LEVEL));

        List<PrismObject<ObjectType>> members = repositoryService.searchObjects(ObjectType.class, objectQuery, null,
                result);
        for (PrismObject<ObjectType> member : members) {
            if (member.asObjectable() instanceof UserType) {
                UserType user = (UserType) member.asObjectable();
                //                if (isManagerOf(user, orgOid)) {
                retval.add(user);
                //                }
            }
        }
        return retval;
    }

    @Override
    public boolean isManagerOf(UserType user, String orgOid) {
        for (ObjectReferenceType objectReferenceType : user.getParentOrgRef()) {
            if (orgOid.equals(objectReferenceType.getOid())
                    && SchemaConstants.ORG_MANAGER.equals(objectReferenceType.getRelation())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isManager(UserType user) {
        for (ObjectReferenceType objectReferenceType : user.getParentOrgRef()) {
            if (SchemaConstants.ORG_MANAGER.equals(objectReferenceType.getRelation())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isManagerOfOrgType(UserType user, String orgType) throws SchemaException {
        for (ObjectReferenceType objectReferenceType : user.getParentOrgRef()) {
            if (SchemaConstants.ORG_MANAGER.equals(objectReferenceType.getRelation())) {
                OrgType org = getOrgByOid(objectReferenceType.getOid());
                if (org.getOrgType().contains(orgType)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean isMemberOf(UserType user, String orgOid) {
        for (ObjectReferenceType objectReferenceType : user.getParentOrgRef()) {
            if (orgOid.equals(objectReferenceType.getOid())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getPlaintextUserPassword(UserType user) throws EncryptionException {
        if (user == null || user.getCredentials() == null || user.getCredentials().getPassword() == null) {
            return null; // todo log a warning here?
        }
        ProtectedStringType protectedStringType = user.getCredentials().getPassword().getValue();
        if (protectedStringType != null) {
            return protector.decryptString(protectedStringType);
        } else {
            return null;
        }
    }

    @Override
    public String getPlaintextAccountPassword(ShadowType account) throws EncryptionException {
        if (account == null || account.getCredentials() == null || account.getCredentials().getPassword() == null) {
            return null; // todo log a warning here?
        }
        ProtectedStringType protectedStringType = account.getCredentials().getPassword().getValue();
        if (protectedStringType != null) {
            return protector.decryptString(protectedStringType);
        } else {
            return null;
        }
    }

    @Override
    public String getPlaintextAccountPasswordFromDelta(ObjectDelta<? extends ShadowType> delta)
            throws EncryptionException {

        if (delta.isAdd()) {
            ShadowType newShadow = delta.getObjectToAdd().asObjectable();
            return getPlaintextAccountPassword(newShadow);
        }
        if (!delta.isModify()) {
            return null;
        }

        List<ProtectedStringType> passwords = new ArrayList<ProtectedStringType>();
        for (ItemDelta itemDelta : delta.getModifications()) {
            takePasswordsFromItemDelta(passwords, itemDelta);
        }
        LOGGER.trace("Found " + passwords.size() + " password change value(s)");
        if (!passwords.isEmpty()) {
            return protector.decryptString(passwords.get(passwords.size() - 1));
        } else {
            return null;
        }
    }

    private void takePasswordsFromItemDelta(List<ProtectedStringType> passwords, ItemDelta itemDelta) {
        if (itemDelta.isDelete()) {
            return;
        }

        if (itemDelta.getPath().equivalent(
                new ItemPath(ShadowType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE))) {
            LOGGER.trace("Found password value add/modify delta");
            Collection<PrismPropertyValue<ProtectedStringType>> values = itemDelta.isAdd()
                    ? itemDelta.getValuesToAdd()
                    : itemDelta.getValuesToReplace();
            for (PrismPropertyValue<ProtectedStringType> value : values) {
                passwords.add(value.getValue());
            }
        } else if (itemDelta.getPath()
                .equivalent(new ItemPath(ShadowType.F_CREDENTIALS, CredentialsType.F_PASSWORD))) {
            LOGGER.trace("Found password add/modify delta");
            Collection<PrismContainerValue<PasswordType>> values = itemDelta.isAdd() ? itemDelta.getValuesToAdd()
                    : itemDelta.getValuesToReplace();
            for (PrismContainerValue<PasswordType> value : values) {
                if (value.asContainerable().getValue() != null) {
                    passwords.add(value.asContainerable().getValue());
                }
            }
        } else if (itemDelta.getPath().equivalent(new ItemPath(ShadowType.F_CREDENTIALS))) {
            LOGGER.trace("Found credentials add/modify delta");
            Collection<PrismContainerValue<CredentialsType>> values = itemDelta.isAdd() ? itemDelta.getValuesToAdd()
                    : itemDelta.getValuesToReplace();
            for (PrismContainerValue<CredentialsType> value : values) {
                if (value.asContainerable().getPassword() != null
                        && value.asContainerable().getPassword().getValue() != null) {
                    passwords.add(value.asContainerable().getPassword().getValue());
                }
            }
        }
    }

    @Override
    public String getPlaintextUserPasswordFromDeltas(List<ObjectDelta<UserType>> objectDeltas)
            throws EncryptionException {

        List<ProtectedStringType> passwords = new ArrayList<ProtectedStringType>();

        for (ObjectDelta<UserType> delta : objectDeltas) {

            if (delta.isAdd()) {
                UserType newUser = delta.getObjectToAdd().asObjectable();
                return getPlaintextUserPassword(newUser); // for simplicity we do not look for other values
            }

            if (!delta.isModify()) {
                continue;
            }

            for (ItemDelta itemDelta : delta.getModifications()) {
                takePasswordsFromItemDelta(passwords, itemDelta);
            }
        }
        LOGGER.trace("Found " + passwords.size() + " password change value(s)");
        if (!passwords.isEmpty()) {
            return protector.decryptString(passwords.get(passwords.size() - 1));
        } else {
            return null;
        }
    }

    public <F extends ObjectType> boolean hasLinkedAccount(String resourceOid) {
        LensContext<F> ctx = ModelExpressionThreadLocalHolder.getLensContext();
        if (ctx == null) {
            throw new IllegalStateException("No lens context");
        }
        LensFocusContext<F> focusContext = ctx.getFocusContext();
        if (focusContext == null) {
            throw new IllegalStateException("No focus in lens context");
        }

        ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal();

        ResourceShadowDiscriminator rat = new ResourceShadowDiscriminator(resourceOid, ShadowKindType.ACCOUNT,
                null);
        LensProjectionContext projectionContext = ctx.findProjectionContext(rat);
        if (projectionContext == null) {
            // but check if it is not among list of deleted contexts
            if (scriptContext == null || scriptContext.isEvaluateNew()) {
                return false;
            }
            // evaluating old state
            for (ResourceShadowDiscriminator deletedOne : ctx.getHistoricResourceObjects()) {
                if (resourceOid.equals(deletedOne.getResourceOid())
                        && deletedOne.getKind() == ShadowKindType.ACCOUNT && deletedOne.getIntent() == null
                        || "default".equals(deletedOne.getIntent())) { // TODO implement this seriously
                    LOGGER.trace("Found deleted one: {}", deletedOne); // TODO remove
                    return true;
                }
            }
            return false;
        }

        if (projectionContext.isThombstone()) {
            return false;
        }

        SynchronizationPolicyDecision synchronizationPolicyDecision = projectionContext
                .getSynchronizationPolicyDecision();
        SynchronizationIntent synchronizationIntent = projectionContext.getSynchronizationIntent();
        if (scriptContext == null) {
            if (synchronizationPolicyDecision == null) {
                if (synchronizationIntent == SynchronizationIntent.DELETE
                        || synchronizationIntent == SynchronizationIntent.UNLINK) {
                    return false;
                } else {
                    return true;
                }
            } else {
                if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE
                        || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) {
                    return false;
                } else {
                    return true;
                }
            }
        } else if (scriptContext.isEvaluateNew()) {
            // Evaluating new state
            if (focusContext.isDelete()) {
                return false;
            }
            if (synchronizationPolicyDecision == null) {
                if (synchronizationIntent == SynchronizationIntent.DELETE
                        || synchronizationIntent == SynchronizationIntent.UNLINK) {
                    return false;
                } else {
                    return true;
                }
            } else {
                if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE
                        || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) {
                    return false;
                } else {
                    return true;
                }
            }
        } else {
            // Evaluating old state
            if (focusContext.isAdd()) {
                return false;
            }
            if (synchronizationPolicyDecision == null) {
                if (synchronizationIntent == SynchronizationIntent.ADD) {
                    return false;
                } else {
                    return true;
                }
            } else {
                if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) {
                    return false;
                } else {
                    return true;
                }
            }
        }
    }

    @Override
    public <F extends FocusType> boolean isDirectlyAssigned(F focusType, String targetOid) {
        for (AssignmentType assignment : focusType.getAssignment()) {
            ObjectReferenceType targetRef = assignment.getTargetRef();
            if (targetRef != null && targetRef.getOid().equals(targetOid)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public <F extends FocusType> boolean isDirectlyAssigned(F focusType, ObjectType target) {
        return isDirectlyAssigned(focusType, target.getOid());
    }

    @Override
    public boolean isDirectlyAssigned(String targetOid) {
        LensContext<? extends FocusType> ctx = ModelExpressionThreadLocalHolder.getLensContext();
        if (ctx == null) {
            throw new IllegalStateException("No lens context");
        }
        LensFocusContext<? extends FocusType> focusContext = ctx.getFocusContext();
        if (focusContext == null) {
            throw new IllegalStateException("No focus in lens context");
        }

        PrismObject<? extends FocusType> focus;
        ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal();
        if (scriptContext == null) {
            focus = focusContext.getObjectAny();
        } else if (scriptContext.isEvaluateNew()) {
            // Evaluating new state
            if (focusContext.isDelete()) {
                return false;
            }
            focus = focusContext.getObjectNew();
        } else {
            // Evaluating old state
            if (focusContext.isAdd()) {
                return false;
            }
            focus = focusContext.getObjectOld();
        }
        if (focus == null) {
            return false;
        }
        return isDirectlyAssigned(focus.asObjectable(), targetOid);
    }

    @Override
    public boolean isDirectlyAssigned(ObjectType target) {
        return isDirectlyAssigned(target.getOid());
    }

    @Override
    public ShadowType getLinkedShadow(FocusType focus, ResourceType resource)
            throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException {
        return getLinkedShadow(focus, resource.getOid());
    }

    @Override
    public ShadowType getLinkedShadow(FocusType focus, String resourceOid)
            throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException {
        if (focus == null) {
            return null;
        }
        List<ObjectReferenceType> linkRefs = focus.getLinkRef();
        for (ObjectReferenceType linkRef : linkRefs) {
            ShadowType shadowType;
            try {
                shadowType = getObject(ShadowType.class, linkRef.getOid());
            } catch (ObjectNotFoundException e) {
                // Shadow is gone in the meantime. MidPoint will resolve that by itself.
                // It is safe to ignore this error in this method.
                LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus
                        + " because it no longer exists");
                continue;
            }
            if (shadowType.getResourceRef().getOid().equals(resourceOid)) {
                return shadowType;
            }
        }
        return null;
    }

    @Override
    public ShadowType getLinkedShadow(FocusType focus, String resourceOid, ShadowKindType kind, String intent)
            throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException {
        List<ObjectReferenceType> linkRefs = focus.getLinkRef();
        for (ObjectReferenceType linkRef : linkRefs) {
            ShadowType shadowType;
            try {
                shadowType = getObject(ShadowType.class, linkRef.getOid());
            } catch (ObjectNotFoundException e) {
                // Shadow is gone in the meantime. MidPoint will resolve that by itself.
                // It is safe to ignore this error in this method.
                LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus
                        + " because it no longer exists");
                continue;
            }
            if (ShadowUtil.matches(shadowType, resourceOid, kind, intent)) {
                return shadowType;
            }
        }
        return null;
    }

    public <T> Integer countAccounts(String resourceOid, QName attributeName, T attributeValue)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + ".countAccounts");
        ResourceType resourceType = modelObjectResolver.getObjectSimple(ResourceType.class, resourceOid, null, null,
                result);
        return countAccounts(resourceType, attributeName, attributeValue, getCurrentTask(), result);
    }

    public <T> Integer countAccounts(ResourceType resourceType, QName attributeName, T attributeValue)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + ".countAccounts");
        return countAccounts(resourceType, attributeName, attributeValue, getCurrentTask(), result);
    }

    public <T> Integer countAccounts(ResourceType resourceType, String attributeName, T attributeValue)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + ".countAccounts");
        QName attributeQName = new QName(ResourceTypeUtil.getResourceNamespace(resourceType), attributeName);
        return countAccounts(resourceType, attributeQName, attributeValue, getCurrentTask(), result);
    }

    private <T> Integer countAccounts(ResourceType resourceType, QName attributeName, T attributeValue, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        RefinedResourceSchema rSchema = RefinedResourceSchema.getRefinedSchema(resourceType);
        RefinedObjectClassDefinition rAccountDef = rSchema.getDefaultRefinedDefinition(ShadowKindType.ACCOUNT);
        RefinedAttributeDefinition attrDef = rAccountDef.findAttributeDefinition(attributeName);
        EqualFilter idFilter = EqualFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, attrDef.getName()),
                attrDef, attributeValue);
        EqualFilter ocFilter = EqualFilter.createEqual(ShadowType.F_OBJECT_CLASS, ShadowType.class, prismContext,
                null, rAccountDef.getObjectClassDefinition().getTypeName());
        RefFilter resourceRefFilter = RefFilter.createReferenceEqual(ShadowType.F_RESOURCE_REF, ShadowType.class,
                resourceType);
        AndFilter filter = AndFilter.createAnd(idFilter, ocFilter, resourceRefFilter);
        ObjectQuery query = ObjectQuery.createObjectQuery(filter);
        return modelObjectResolver.countObjects(ShadowType.class, query, null, task, result);
    }

    public <T> boolean isUniquePropertyValue(ObjectType objectType, String propertyPathString, T propertyValue)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        Validate.notEmpty(propertyPathString, "Empty property path");
        OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + ".isUniquePropertyValue");
        ItemPath propertyPath = new XPathHolder(propertyPathString).toItemPath();
        return isUniquePropertyValue(objectType, propertyPath, propertyValue, getCurrentTask(), result);
    }

    private <T> boolean isUniquePropertyValue(final ObjectType objectType, ItemPath propertyPath, T propertyValue,
            Task task, OperationResult result) throws SchemaException, ObjectNotFoundException,
            CommunicationException, ConfigurationException, SecurityViolationException {
        List<? extends ObjectType> conflictingObjects = getObjectsInConflictOnPropertyValue(objectType,
                propertyPath, propertyValue, DefaultMatchingRule.NAME, false, task, result);
        return conflictingObjects.isEmpty();
    }

    public <O extends ObjectType, T> List<O> getObjectsInConflictOnPropertyValue(O objectType,
            String propertyPathString, T propertyValue, boolean getAllConflicting) throws SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
        return getObjectsInConflictOnPropertyValue(objectType, propertyPathString, propertyValue,
                DefaultMatchingRule.NAME.getLocalPart(), getAllConflicting);
    }

    public <O extends ObjectType, T> List<O> getObjectsInConflictOnPropertyValue(O objectType,
            String propertyPathString, T propertyValue, String matchingRuleName, boolean getAllConflicting)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        Validate.notEmpty(propertyPathString, "Empty property path");
        OperationResult result = getCurrentResult(
                MidpointFunctions.class.getName() + ".getObjectsInConflictOnPropertyValue");
        ItemPath propertyPath = new XPathHolder(propertyPathString).toItemPath();
        QName matchingRuleQName = new QName(matchingRuleName); // no namespace for now
        return getObjectsInConflictOnPropertyValue(objectType, propertyPath, propertyValue, matchingRuleQName,
                getAllConflicting, getCurrentTask(), result);
    }

    private <O extends ObjectType, T> List<O> getObjectsInConflictOnPropertyValue(final O objectType,
            ItemPath propertyPath, T propertyValue, QName matchingRule, final boolean getAllConflicting, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        Validate.notNull(objectType, "Null object");
        Validate.notNull(propertyPath, "Null property path");
        Validate.notNull(propertyValue, "Null property value");
        PrismPropertyDefinition<T> propertyDefinition = objectType.asPrismObject().getDefinition()
                .findPropertyDefinition(propertyPath);
        EqualFilter<T> filter = EqualFilter.createEqual(propertyPath, propertyDefinition, matchingRule,
                propertyValue);
        ObjectQuery query = ObjectQuery.createObjectQuery(filter);
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Determining uniqueness of property {} using query:\n{}", propertyPath, query.debugDump());
        }

        final List<O> conflictingObjects = new ArrayList<>();
        ResultHandler<O> handler = new ResultHandler<O>() {
            @Override
            public boolean handle(PrismObject<O> object, OperationResult parentResult) {
                if (objectType.getOid() == null) {
                    // We have found a conflicting object
                    conflictingObjects.add(object.asObjectable());
                    return getAllConflicting;
                } else {
                    if (objectType.getOid().equals(object.getOid())) {
                        // We have found ourselves. No conflict (yet). Just go on.
                        return true;
                    } else {
                        // We have found someone else. Conflict.
                        conflictingObjects.add(object.asObjectable());
                        return getAllConflicting;
                    }
                }
            }
        };

        modelObjectResolver.searchIterative((Class) objectType.getClass(), query, null, handler, task, result);

        return conflictingObjects;
    }

    public <T> boolean isUniqueAccountValue(ResourceType resourceType, ShadowType shadowType, String attributeName,
            T attributeValue) throws SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        Validate.notEmpty(attributeName, "Empty attribute name");
        OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + ".isUniqueAccountValue");
        QName attributeQName = new QName(ResourceTypeUtil.getResourceNamespace(resourceType), attributeName);
        return isUniqueAccountValue(resourceType, shadowType, attributeQName, attributeValue, getCurrentTask(),
                result);
    }

    private <T> boolean isUniqueAccountValue(ResourceType resourceType, final ShadowType shadowType,
            QName attributeName, T attributeValue, Task task, OperationResult result) throws SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
        Validate.notNull(resourceType, "Null resource");
        Validate.notNull(shadowType, "Null shadow");
        Validate.notNull(attributeName, "Null attribute name");
        Validate.notNull(attributeValue, "Null attribute value");
        RefinedResourceSchema rSchema = RefinedResourceSchema.getRefinedSchema(resourceType);
        RefinedObjectClassDefinition rAccountDef = rSchema.getDefaultRefinedDefinition(ShadowKindType.ACCOUNT);
        RefinedAttributeDefinition attrDef = rAccountDef.findAttributeDefinition(attributeName);
        EqualFilter idFilter = EqualFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, attrDef.getName()),
                attrDef, attributeValue);
        EqualFilter ocFilter = EqualFilter.createEqual(ShadowType.F_OBJECT_CLASS, ShadowType.class, prismContext,
                null, rAccountDef.getObjectClassDefinition().getTypeName());
        RefFilter resourceRefFilter = RefFilter.createReferenceEqual(ShadowType.F_RESOURCE_REF, ShadowType.class,
                resourceType);
        AndFilter filter = AndFilter.createAnd(idFilter, ocFilter, resourceRefFilter);
        ObjectQuery query = ObjectQuery.createObjectQuery(filter);
        LOGGER.trace("Determining uniqueness of attribute {} using query:\n{}", attributeName, query.debugDump());

        final Holder<Boolean> isUniqueHolder = new Holder<Boolean>(true);
        ResultHandler<ShadowType> handler = new ResultHandler<ShadowType>() {
            @Override
            public boolean handle(PrismObject<ShadowType> object, OperationResult parentResult) {
                if (shadowType == null || shadowType.getOid() == null) {
                    // We have found a conflicting object
                    isUniqueHolder.setValue(false);
                    return false;
                } else {
                    if (shadowType.getOid().equals(object.getOid())) {
                        // We have found ourselves. No conflict (yet). Just go on.
                        return true;
                    } else {
                        // We have found someone else. Conflict.
                        isUniqueHolder.setValue(false);
                        return false;
                    }
                }
            }
        };

        modelObjectResolver.searchIterative(ShadowType.class, query, null, handler, task, result);

        return isUniqueHolder.getValue();
    }

    private Task getCurrentTask() {
        return ModelExpressionThreadLocalHolder.getCurrentTask();
    }

    private OperationResult getCurrentResult() {
        return ModelExpressionThreadLocalHolder.getCurrentResult();
    }

    private OperationResult getCurrentResult(String operationName) {
        OperationResult currentResult = ModelExpressionThreadLocalHolder.getCurrentResult();
        if (currentResult == null) {
            return new OperationResult(operationName);
        } else {
            return currentResult;
        }
    }

    private OperationResult createSubresult(String operationName) {
        OperationResult currentResult = ModelExpressionThreadLocalHolder.getCurrentResult();
        if (currentResult == null) {
            return new OperationResult(operationName);
        } else {
            return currentResult.createSubresult(operationName);
        }
    }

    private OperationResult createMinorSubresult(String operationName) {
        OperationResult currentResult = ModelExpressionThreadLocalHolder.getCurrentResult();
        if (currentResult == null) {
            return new OperationResult(operationName);
        } else {
            return currentResult.createMinorSubresult(operationName);
        }
    }

    // functions working with ModelContext

    @Override
    public ModelContext unwrapModelContext(LensContextType lensContextType)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
        return LensContext.fromLensContextType(lensContextType, prismContext, provisioningService,
                getCurrentResult(MidpointFunctions.class.getName() + ".getObject"));
    }

    public LensContextType wrapModelContext(LensContext<?> lensContext) throws SchemaException {
        return lensContext.toPrismContainer().getValue().asContainerable();
    }

    // Convenience functions

    @Override
    public <T extends ObjectType> T createEmptyObject(Class<T> type) {
        PrismObjectDefinition<T> objectDefinition = prismContext.getSchemaRegistry()
                .findObjectDefinitionByCompileTimeClass(type);
        PrismObject<T> object = objectDefinition.instantiate();
        return object.asObjectable();
    }

    @Override
    public <T extends ObjectType> T createEmptyObjectWithName(Class<T> type, String name) {
        T objectType = createEmptyObject(type);
        objectType.setName(new PolyStringType(name));
        return objectType;
    }

    @Override
    public <T extends ObjectType> T createEmptyObjectWithName(Class<T> type, PolyString name) {
        T objectType = createEmptyObject(type);
        objectType.setName(new PolyStringType(name));
        return objectType;
    }

    @Override
    public <T extends ObjectType> T createEmptyObjectWithName(Class<T> type, PolyStringType name) {
        T objectType = createEmptyObject(type);
        objectType.setName(name);
        return objectType;
    }

    // Functions accessing modelService

    @Override
    public <T extends ObjectType> T resolveReference(ObjectReferenceType reference) throws ObjectNotFoundException,
            SchemaException, CommunicationException, ConfigurationException, SecurityViolationException {
        if (reference == null) {
            return null;
        }
        QName type = reference.getType(); // TODO what about implicitly specified types, like in resourceRef?
        PrismObjectDefinition<T> objectDefinition = prismContext.getSchemaRegistry()
                .findObjectDefinitionByType(reference.getType());
        if (objectDefinition == null) {
            throw new SchemaException("No definition for type " + type);
        }
        return modelService.getObject(objectDefinition.getCompileTimeClass(), reference.getOid(), null,
                getCurrentTask(), getCurrentResult()).asObjectable();
    }

    @Override
    public <T extends ObjectType> T resolveReferenceIfExists(ObjectReferenceType reference)
            throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException {
        try {
            return resolveReference(reference);
        } catch (ObjectNotFoundException e) {
            return null;
        }
    }

    @Override
    public <T extends ObjectType> T getObject(Class<T> type, String oid,
            Collection<SelectorOptions<GetOperationOptions>> options) throws ObjectNotFoundException,
            SchemaException, CommunicationException, ConfigurationException, SecurityViolationException {
        return modelService.getObject(type, oid, options, getCurrentTask(), getCurrentResult()).asObjectable();
    }

    @Override
    public <T extends ObjectType> T getObject(Class<T> type, String oid)
            throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        PrismObject<T> prismObject = modelService.getObject(type, oid, null, getCurrentTask(), getCurrentResult());
        return prismObject.asObjectable();
    }

    @Override
    public void executeChanges(Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options)
            throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException,
            SecurityViolationException {
        modelService.executeChanges(deltas, options, getCurrentTask(), getCurrentResult());
    }

    @Override
    public void executeChanges(Collection<ObjectDelta<? extends ObjectType>> deltas)
            throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException,
            SecurityViolationException {
        modelService.executeChanges(deltas, null, getCurrentTask(), getCurrentResult());
    }

    @Override
    public void executeChanges(ObjectDelta<? extends ObjectType>... deltas) throws ObjectAlreadyExistsException,
            ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, PolicyViolationException, SecurityViolationException {
        Collection<ObjectDelta<? extends ObjectType>> deltaCollection = MiscSchemaUtil.createCollection(deltas);
        modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> String addObject(PrismObject<T> newObject, ModelExecuteOptions options)
            throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException,
            SecurityViolationException {
        ObjectDelta<T> delta = ObjectDelta.createAddDelta(newObject);
        Collection<ObjectDelta<? extends ObjectType>> deltaCollection = MiscSchemaUtil.createCollection(delta);
        modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult());
        return delta.getOid();
    }

    @Override
    public <T extends ObjectType> String addObject(PrismObject<T> newObject) throws ObjectAlreadyExistsException,
            ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, PolicyViolationException, SecurityViolationException {
        return addObject(newObject, null);
    }

    @Override
    public <T extends ObjectType> String addObject(T newObject, ModelExecuteOptions options)
            throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException,
            SecurityViolationException {
        return addObject(newObject.asPrismObject(), options);
    }

    @Override
    public <T extends ObjectType> String addObject(T newObject) throws ObjectAlreadyExistsException,
            ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, PolicyViolationException, SecurityViolationException {
        return addObject(newObject.asPrismObject(), null);
    }

    @Override
    public <T extends ObjectType> void modifyObject(ObjectDelta<T> modifyDelta, ModelExecuteOptions options)
            throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException,
            SecurityViolationException {
        Collection<ObjectDelta<? extends ObjectType>> deltaCollection = MiscSchemaUtil
                .createCollection(modifyDelta);
        modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> void modifyObject(ObjectDelta<T> modifyDelta) throws ObjectAlreadyExistsException,
            ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, PolicyViolationException, SecurityViolationException {
        Collection<ObjectDelta<? extends ObjectType>> deltaCollection = MiscSchemaUtil
                .createCollection(modifyDelta);
        modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> void deleteObject(Class<T> type, String oid, ModelExecuteOptions options)
            throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException,
            SecurityViolationException {
        ObjectDelta<T> deleteDelta = ObjectDelta.createDeleteDelta(type, oid, prismContext);
        Collection<ObjectDelta<? extends ObjectType>> deltaCollection = MiscSchemaUtil
                .createCollection(deleteDelta);
        modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> void deleteObject(Class<T> type, String oid) throws ObjectAlreadyExistsException,
            ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, PolicyViolationException, SecurityViolationException {
        ObjectDelta<T> deleteDelta = ObjectDelta.createDeleteDelta(type, oid, prismContext);
        Collection<ObjectDelta<? extends ObjectType>> deltaCollection = MiscSchemaUtil
                .createCollection(deleteDelta);
        modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <F extends FocusType> void recompute(Class<F> type, String oid)
            throws SchemaException, PolicyViolationException, ExpressionEvaluationException,
            ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        modelService.recompute(type, oid, getCurrentTask(), getCurrentResult());
    }

    @Override
    public PrismObject<UserType> findShadowOwner(String accountOid)
            throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException {
        return modelService.findShadowOwner(accountOid, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> List<T> searchObjects(Class<T> type, ObjectQuery query,
            Collection<SelectorOptions<GetOperationOptions>> options) throws SchemaException,
            ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException {
        return MiscSchemaUtil.toObjectableList(
                modelService.searchObjects(type, query, options, getCurrentTask(), getCurrentResult()));
    }

    @Override
    public <T extends ObjectType> List<T> searchObjects(Class<T> type, ObjectQuery query) throws SchemaException,
            ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException {
        return MiscSchemaUtil.toObjectableList(
                modelService.searchObjects(type, query, null, getCurrentTask(), getCurrentResult()));
    }

    @Override
    public <T extends ObjectType> void searchObjectsIterative(Class<T> type, ObjectQuery query,
            ResultHandler<T> handler, Collection<SelectorOptions<GetOperationOptions>> options)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
            SecurityViolationException {
        modelService.searchObjectsIterative(type, query, handler, options, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> void searchObjectsIterative(Class<T> type, ObjectQuery query,
            ResultHandler<T> handler) throws SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        modelService.searchObjectsIterative(type, query, handler, null, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> T searchObjectByName(Class<T> type, String name)
            throws SecurityViolationException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SchemaException {
        ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext);
        List<PrismObject<T>> foundObjects = modelService.searchObjects(type, nameQuery, null, getCurrentTask(),
                getCurrentResult());
        if (foundObjects.isEmpty()) {
            return null;
        }
        if (foundObjects.size() > 1) {
            throw new IllegalStateException(
                    "More than one object found for type " + type + " and name '" + name + "'");
        }
        return foundObjects.iterator().next().asObjectable();
    }

    @Override
    public <T extends ObjectType> T searchObjectByName(Class<T> type, PolyString name)
            throws SecurityViolationException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SchemaException {
        ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext);
        List<PrismObject<T>> foundObjects = modelService.searchObjects(type, nameQuery, null, getCurrentTask(),
                getCurrentResult());
        if (foundObjects.isEmpty()) {
            return null;
        }
        if (foundObjects.size() > 1) {
            throw new IllegalStateException(
                    "More than one object found for type " + type + " and name '" + name + "'");
        }
        return foundObjects.iterator().next().asObjectable();
    }

    @Override
    public <T extends ObjectType> T searchObjectByName(Class<T> type, PolyStringType name)
            throws SecurityViolationException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SchemaException {
        ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext);
        List<PrismObject<T>> foundObjects = modelService.searchObjects(type, nameQuery, null, getCurrentTask(),
                getCurrentResult());
        if (foundObjects.isEmpty()) {
            return null;
        }
        if (foundObjects.size() > 1) {
            throw new IllegalStateException(
                    "More than one object found for type " + type + " and name '" + name + "'");
        }
        return foundObjects.iterator().next().asObjectable();
    }

    @Override
    public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query,
            Collection<SelectorOptions<GetOperationOptions>> options) throws SchemaException,
            ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException {
        return modelService.countObjects(type, query, options, getCurrentTask(), getCurrentResult());
    }

    @Override
    public <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query) throws SchemaException,
            ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException {
        return modelService.countObjects(type, query, null, getCurrentTask(), getCurrentResult());
    }

    @Override
    public OperationResult testResource(String resourceOid) throws ObjectNotFoundException {
        return modelService.testResource(resourceOid, getCurrentTask());
    }

    @Override
    public ObjectDeltaType getResourceDelta(ModelContext context, String resourceOid) throws SchemaException {
        List<ObjectDelta<ShadowType>> deltas = new ArrayList<>();
        for (Object modelProjectionContextObject : context.getProjectionContexts()) {
            LensProjectionContext lensProjectionContext = (LensProjectionContext) modelProjectionContextObject;
            if (lensProjectionContext.getResourceShadowDiscriminator() != null && resourceOid
                    .equals(lensProjectionContext.getResourceShadowDiscriminator().getResourceOid())) {
                deltas.add(lensProjectionContext.getDelta()); // union of primary and secondary deltas
            }
        }
        ObjectDelta<ShadowType> sum = ObjectDelta.summarize(deltas);
        return DeltaConvertor.toObjectDeltaType(sum);
    }

    public long getSequenceCounter(String sequenceOid) throws ObjectNotFoundException, SchemaException {
        return SequentialValueExpressionEvaluator.getSequenceCounter(sequenceOid, repositoryService,
                getCurrentResult());
    }
}