com.evolveum.midpoint.provisioning.impl.ShadowCaretaker.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.provisioning.impl.ShadowCaretaker.java

Source

/*
 * Copyright (c) 2010-2018 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.provisioning.impl;

import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.common.crypto.CryptoUtil;
import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.ShadowDiscriminatorObjectDelta;
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.*;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.query.*;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.provisioning.api.*;
import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler;
import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler.FailedOperation;
import com.evolveum.midpoint.provisioning.consistency.impl.ErrorHandlerFactory;
import com.evolveum.midpoint.provisioning.ucf.api.Change;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance;
import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException;
import com.evolveum.midpoint.provisioning.util.ProvisioningUtil;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.*;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalCounters;
import com.evolveum.midpoint.schema.internals.InternalMonitor;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.processor.*;
import com.evolveum.midpoint.schema.result.AsynchronousOperationResult;
import com.evolveum.midpoint.schema.result.AsynchronousOperationReturnValue;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.schema.util.SchemaDebugUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.Holder;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.*;
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.resource.capabilities_3.CountObjectsCapabilityType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CountObjectsSimulateType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType;
import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType;
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 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 javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Component that takes care of some shadow maintenance, such as applying definitions, applying pending
 * operations and so on.
 * 
 * Important: this component should be pretty much stand-alone low-level component. It should NOT have
 * any dependencies on ShadowCache, ShadowManager or any other provisioning components (except util components).
 * 
 * @author Radovan Semancik
 */
@Component
public class ShadowCaretaker {

    @Autowired
    private Clock clock;
    @Autowired
    private PrismContext prismContext;

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

    public void applyAttributesDefinition(ProvisioningContext ctx, ObjectDelta<ShadowType> delta)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException,
            ExpressionEvaluationException {
        if (delta.isAdd()) {
            applyAttributesDefinition(ctx, delta.getObjectToAdd());
        } else if (delta.isModify()) {
            for (ItemDelta<?, ?> itemDelta : delta.getModifications()) {
                if (SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getParentPath())) {
                    applyAttributeDefinition(ctx, delta, itemDelta);
                } else if (SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getPath())) {
                    if (itemDelta.isAdd()) {
                        for (PrismValue value : itemDelta.getValuesToAdd()) {
                            applyAttributeDefinition(ctx, value);
                        }
                    }
                    if (itemDelta.isReplace()) {
                        for (PrismValue value : itemDelta.getValuesToReplace()) {
                            applyAttributeDefinition(ctx, value);
                        }
                    }
                }
            }
        }
    }

    // value should be a value of attributes container
    private void applyAttributeDefinition(ProvisioningContext ctx, PrismValue value) throws SchemaException,
            ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException {
        if (!(value instanceof PrismContainerValue)) {
            return; // should never occur
        }
        PrismContainerValue<ShadowAttributesType> pcv = (PrismContainerValue<ShadowAttributesType>) value;
        for (Item item : pcv.getItems()) {
            ItemDefinition itemDef = item.getDefinition();
            if (itemDef == null || !(itemDef instanceof ResourceAttributeDefinition)) {
                QName attributeName = item.getElementName();
                ResourceAttributeDefinition attributeDefinition = ctx.getObjectClassDefinition()
                        .findAttributeDefinition(attributeName);
                if (attributeDefinition == null) {
                    throw new SchemaException("No definition for attribute " + attributeName);
                }
                if (itemDef != null) {
                    // We are going to rewrite the definition anyway. Let's just
                    // do some basic checks first
                    if (!QNameUtil.match(itemDef.getTypeName(), attributeDefinition.getTypeName())) {
                        throw new SchemaException(
                                "The value of type " + itemDef.getTypeName() + " cannot be applied to attribute "
                                        + attributeName + " which is of type " + attributeDefinition.getTypeName());
                    }
                }
                item.applyDefinition(attributeDefinition);
            }
        }
    }

    private <V extends PrismValue, D extends ItemDefinition> void applyAttributeDefinition(ProvisioningContext ctx,
            ObjectDelta<ShadowType> delta, ItemDelta<V, D> itemDelta) throws SchemaException,
            ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException {
        if (!SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getParentPath())) {
            // just to be sure
            return;
        }
        D itemDef = itemDelta.getDefinition();
        if (itemDef == null || !(itemDef instanceof ResourceAttributeDefinition)) {
            QName attributeName = itemDelta.getElementName();
            ResourceAttributeDefinition attributeDefinition = ctx.getObjectClassDefinition()
                    .findAttributeDefinition(attributeName);
            if (attributeDefinition == null) {
                throw new SchemaException(
                        "No definition for attribute " + attributeName + " in object delta " + delta);
            }
            if (itemDef != null) {
                // We are going to rewrite the definition anyway. Let's just do
                // some basic checks first
                if (!QNameUtil.match(itemDef.getTypeName(), attributeDefinition.getTypeName())) {
                    throw new SchemaException(
                            "The value of type " + itemDef.getTypeName() + " cannot be applied to attribute "
                                    + attributeName + " which is of type " + attributeDefinition.getTypeName());
                }
            }
            itemDelta.applyDefinition((D) attributeDefinition);
        }
    }

    public ProvisioningContext applyAttributesDefinition(ProvisioningContext ctx, PrismObject<ShadowType> shadow)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException,
            ExpressionEvaluationException {
        ProvisioningContext subctx = ctx.spawn(shadow);
        subctx.assertDefinition();
        RefinedObjectClassDefinition objectClassDefinition = subctx.getObjectClassDefinition();

        PrismContainer<ShadowAttributesType> attributesContainer = shadow.findContainer(ShadowType.F_ATTRIBUTES);
        if (attributesContainer != null) {
            if (attributesContainer instanceof ResourceAttributeContainer) {
                if (attributesContainer.getDefinition() == null) {
                    attributesContainer
                            .applyDefinition(objectClassDefinition.toResourceAttributeContainerDefinition());
                }
            } else {
                try {
                    // We need to convert <attributes> to
                    // ResourceAttributeContainer
                    ResourceAttributeContainer convertedContainer = ResourceAttributeContainer
                            .convertFromContainer(attributesContainer, objectClassDefinition);
                    shadow.getValue().replace(attributesContainer, convertedContainer);
                } catch (SchemaException e) {
                    throw new SchemaException(e.getMessage() + " in " + shadow, e);
                }
            }
        }

        // We also need to replace the entire object definition to inject
        // correct object class definition here
        // If we don't do this then the patch (delta.applyTo) will not work
        // correctly because it will not be able to
        // create the attribute container if needed.

        PrismObjectDefinition<ShadowType> objectDefinition = shadow.getDefinition();
        PrismContainerDefinition<ShadowAttributesType> origAttrContainerDef = objectDefinition
                .findContainerDefinition(ShadowType.F_ATTRIBUTES);
        if (origAttrContainerDef == null
                || !(origAttrContainerDef instanceof ResourceAttributeContainerDefinition)) {
            PrismObjectDefinition<ShadowType> clonedDefinition = objectDefinition.cloneWithReplacedDefinition(
                    ShadowType.F_ATTRIBUTES, objectClassDefinition.toResourceAttributeContainerDefinition());
            shadow.setDefinition(clonedDefinition);
        }

        return subctx;
    }

    /**
     * Reapplies definition to the shadow if needed. The definition needs to be
     * reapplied e.g. if the shadow has auxiliary object classes, it if subclass
     * of the object class that was originally requested, etc.
     */
    public ProvisioningContext reapplyDefinitions(ProvisioningContext ctx,
            PrismObject<ShadowType> rawResourceShadow) throws SchemaException, ConfigurationException,
            ObjectNotFoundException, CommunicationException, ExpressionEvaluationException {
        ShadowType rawResourceShadowType = rawResourceShadow.asObjectable();
        QName objectClassQName = rawResourceShadowType.getObjectClass();
        List<QName> auxiliaryObjectClassQNames = rawResourceShadowType.getAuxiliaryObjectClass();
        if (auxiliaryObjectClassQNames.isEmpty()
                && objectClassQName.equals(ctx.getObjectClassDefinition().getTypeName())) {
            // shortcut, no need to reapply anything
            return ctx;
        }
        ProvisioningContext shadowCtx = ctx.spawn(rawResourceShadow);
        shadowCtx.assertDefinition();
        RefinedObjectClassDefinition shadowDef = shadowCtx.getObjectClassDefinition();
        ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(rawResourceShadow);
        attributesContainer.applyDefinition(shadowDef.toResourceAttributeContainerDefinition());
        return shadowCtx;
    }

    public PrismObject<ShadowType> applyPendingOperations(ProvisioningContext ctx, PrismObject<ShadowType> shadow,
            XMLGregorianCalendar now) throws SchemaException, ConfigurationException, ObjectNotFoundException,
            CommunicationException, ExpressionEvaluationException {
        if (shadow == null) {
            return null;
        }
        return applyPendingOperations(ctx, shadow, shadow.asObjectable().getPendingOperation(), false, now);
    }

    public PrismObject<ShadowType> applyPendingOperations(ProvisioningContext ctx, PrismObject<ShadowType> shadow,
            List<PendingOperationType> pendingOperations, boolean skipExecutionPendingOperations,
            XMLGregorianCalendar now) throws SchemaException, ConfigurationException, ObjectNotFoundException,
            CommunicationException, ExpressionEvaluationException {
        if (shadow == null) {
            return null;
        }
        PrismObject<ShadowType> resultShadow = shadow;
        ShadowType resultShadowType = resultShadow.asObjectable();
        if (pendingOperations.isEmpty()) {
            return shadow;
        }
        List<PendingOperationType> sortedOperations = sortPendingOperations(pendingOperations);
        Duration gracePeriod = ProvisioningUtil.getGracePeriod(ctx);
        boolean resourceReadIsCachingOnly = ProvisioningUtil.resourceReadIsCachingOnly(ctx.getResource());
        for (PendingOperationType pendingOperation : sortedOperations) {
            OperationResultStatusType resultStatus = pendingOperation.getResultStatus();
            PendingOperationExecutionStatusType executionStatus = pendingOperation.getExecutionStatus();
            if (resultStatus == OperationResultStatusType.FATAL_ERROR
                    || resultStatus == OperationResultStatusType.NOT_APPLICABLE) {
                continue;
            }
            if (skipExecutionPendingOperations
                    && executionStatus == PendingOperationExecutionStatusType.EXECUTION_PENDING) {
                continue;
            }
            if (ProvisioningUtil.isOverGrace(now, gracePeriod, pendingOperation)) {
                continue;
            }
            if (resourceReadIsCachingOnly) {
                // We are getting the data from our own cache. So we know that all completed operations are already applied in the cache.
                // Re-applying them will mean additional risk of corrupting the data.
                if (resultStatus != null && resultStatus != OperationResultStatusType.IN_PROGRESS
                        && resultStatus != OperationResultStatusType.UNKNOWN) {
                    continue;
                }
            } else {
                // We want to apply all the deltas, even those that are already completed. They might not be reflected on the resource yet.
                // E.g. they may be not be present in the CSV export until the next export cycle is scheduled
            }
            ObjectDeltaType pendingDeltaType = pendingOperation.getDelta();
            ObjectDelta<ShadowType> pendingDelta = DeltaConvertor.createObjectDelta(pendingDeltaType, prismContext);
            if (pendingDelta.isAdd()) {
                if (Boolean.FALSE.equals(resultShadowType.isExists())) {
                    ShadowType shadowType = shadow.asObjectable();
                    resultShadow = pendingDelta.getObjectToAdd().clone();
                    resultShadow.setOid(shadow.getOid());
                    resultShadowType = resultShadow.asObjectable();
                    resultShadowType.setExists(true);
                    resultShadowType.setName(shadowType.getName());
                    List<PendingOperationType> newPendingOperations = resultShadowType.getPendingOperation();
                    for (PendingOperationType pendingOperation2 : shadowType.getPendingOperation()) {
                        newPendingOperations.add(pendingOperation2.clone());
                    }
                    applyAttributesDefinition(ctx, resultShadow);
                }
            }
            if (pendingDelta.isModify()) {
                pendingDelta.applyTo(resultShadow);
            }
            if (pendingDelta.isDelete()) {
                resultShadowType.setDead(true);
                resultShadowType.setExists(false);
            }
        }
        // TODO: check schema, remove non-readable attributes, activation, password, etc.
        //      CredentialsType creds = resultShadowType.getCredentials();
        //      if (creds != null) {
        //         PasswordType passwd = creds.getPassword();
        //         if (passwd != null) {
        //            passwd.setValue(null);
        //         }
        //      }
        return resultShadow;
    }

    public List<PendingOperationType> sortPendingOperations(List<PendingOperationType> pendingOperations) {
        // Copy to mutable list that is not bound to the prism
        List<PendingOperationType> sortedList = new ArrayList<>(pendingOperations.size());
        sortedList.addAll(pendingOperations);
        sortedList.sort((o1, o2) -> XmlTypeConverter.compare(o1.getRequestTimestamp(), o2.getRequestTimestamp()));
        return sortedList;
    }

}