/*
* 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.provisioning.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterEntry;
import com.evolveum.midpoint.prism.query.builder.S_FilterEntry;
import com.evolveum.midpoint.provisioning.util.ProvisioningUtil;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition;
import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.match.MatchingRule;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.Visitor;
import com.evolveum.midpoint.provisioning.api.ResourceOperationDescription;
import com.evolveum.midpoint.provisioning.ucf.api.Change;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SearchResultMetadata;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ResourceAttribute;
import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer;
import com.evolveum.midpoint.schema.result.AsynchronousOperationReturnValue;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.schema.util.SchemaDebugUtil;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
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.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
/**
* Responsibilities:
* Communicate with the repo
* Store results in the repo shadows
* Clean up shadow inconsistencies in repo
*
* Limitations:
* Do NOT communicate with the resource
* means: do NOT do anything with the connector
*
* @author Katarina Valalikova
* @author Radovan Semancik
*
*/
@Component
public class ShadowManager {
@Autowired(required = true)
@Qualifier("cacheRepositoryService")
private RepositoryService repositoryService;
@Autowired(required = true)
private Clock clock;
@Autowired(required = true)
private PrismContext prismContext;
@Autowired(required = true)
private TaskManager taskManager;
@Autowired(required = true)
private MatchingRuleRegistry matchingRuleRegistry;
private static final Trace LOGGER = TraceManager.getTrace(ShadowManager.class);
public void deleteConflictedShadowFromRepo(PrismObject<ShadowType> shadow, OperationResult parentResult){
try{
repositoryService.deleteObject(shadow.getCompileTimeClass(), shadow.getOid(), parentResult);
} catch (Exception ex){
throw new SystemException(ex.getMessage(), ex);
}
}
public ResourceOperationDescription createResourceFailureDescription(
PrismObject<ShadowType> conflictedShadow, ResourceType resource, OperationResult parentResult){
ResourceOperationDescription failureDesc = new ResourceOperationDescription();
failureDesc.setCurrentShadow(conflictedShadow);
ObjectDelta<ShadowType> objectDelta = null;
if (FailedOperationTypeType.ADD == conflictedShadow.asObjectable().getFailedOperationType()) {
objectDelta = ObjectDelta.createAddDelta(conflictedShadow);
}
failureDesc.setObjectDelta(objectDelta);
failureDesc.setResource(resource.asPrismObject());
failureDesc.setResult(parentResult);
failureDesc.setSourceChannel(SchemaConstants.CHANGE_CHANNEL_DISCOVERY.getLocalPart());
return failureDesc;
}
/**
* Locates the appropriate Shadow in repository that corresponds to the
* provided resource object.
*
* DEAD flag is cleared - in memory as well as in repository.
*
* @param parentResult
*
* @return current shadow object that corresponds to provided
* resource object or null if the object does not exist
*/
public PrismObject<ShadowType> lookupShadowInRepository(ProvisioningContext ctx, PrismObject<ShadowType> resourceShadow,
OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
ObjectQuery query = createSearchShadowQuery(ctx, resourceShadow, prismContext,
parentResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Searching for shadow using filter:\n{}",
query.debugDump());
}
// PagingType paging = new PagingType();
// TODO: check for errors
List<PrismObject<ShadowType>> results = repositoryService.searchObjects(ShadowType.class, query, null, parentResult);
MiscSchemaUtil.reduceSearchResult(results);
LOGGER.trace("lookupShadow found {} objects", results.size());
if (results.size() == 0) {
return null;
}
if (results.size() > 1) {
for (PrismObject<ShadowType> result : results) {
LOGGER.trace("Search result:\n{}", result.debugDump());
}
LOGGER.error("More than one shadow found for " + resourceShadow);
// TODO: Better error handling later
throw new IllegalStateException("More than one shadow found for " + resourceShadow);
}
PrismObject<ShadowType> shadow = results.get(0);
checkConsistency(shadow);
if (Boolean.TRUE.equals(shadow.asObjectable().isDead())) {
LOGGER.debug("Repository shadow {} is marked as dead - resetting the flag", ObjectTypeUtil.toShortString(shadow));
shadow.asObjectable().setDead(false);
List<ItemDelta<?, ?>> deltas = DeltaBuilder.deltaFor(ShadowType.class, prismContext).item(ShadowType.F_DEAD).replace().asItemDeltas();
try {
repositoryService.modifyObject(ShadowType.class, shadow.getOid(), deltas, parentResult);
} catch (ObjectAlreadyExistsException e) {
throw new SystemException("Unexpected exception when resetting 'dead' flag: " + e.getMessage(), e);
}
}
return shadow;
}
public PrismObject<ShadowType> lookupShadowInRepository(ProvisioningContext ctx, ResourceAttributeContainer identifierContainer,
OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
ObjectQuery query = createSearchShadowQuery(ctx, identifierContainer.getValue().getItems(), false, prismContext,
parentResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Searching for shadow using filter (repo):\n{}",
query.debugDump());
}
// PagingType paging = new PagingType();
// TODO: check for errors
List<PrismObject<ShadowType>> results;
results = repositoryService.searchObjects(ShadowType.class, query, null, parentResult);
MiscSchemaUtil.reduceSearchResult(results);
LOGGER.trace("lookupShadow found {} objects", results.size());
if (results.size() == 0) {
return null;
}
if (results.size() > 1) {
LOGGER.error("More than one shadow found in repository for " + identifierContainer);
if (LOGGER.isDebugEnabled()) {
for (PrismObject<ShadowType> result : results) {
LOGGER.debug("Conflicting shadow (repo):\n{}", result.debugDump());
}
}
// TODO: Better error handling later
throw new IllegalStateException("More than one shadows found in repository for " + identifierContainer);
}
PrismObject<ShadowType> shadow = results.get(0);
checkConsistency(shadow);
return shadow;
}
public PrismObject<ShadowType> lookupConflictingShadowBySecondaryIdentifiers(
ProvisioningContext ctx, PrismObject<ShadowType> resourceShadow, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
Collection<ResourceAttribute<?>> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(resourceShadow);
List<PrismObject<ShadowType>> results = lookupShadowsBySecondaryIdentifiers(ctx, secondaryIdentifiers, parentResult);
if (results == null || results.size() == 0) {
return null;
}
List<PrismObject<ShadowType>> conflictingShadows = new ArrayList<PrismObject<ShadowType>>();
for (PrismObject<ShadowType> shadow: results){
ShadowType repoShadowType = shadow.asObjectable();
if (shadow != null) {
if (repoShadowType.getFailedOperationType() == null){
LOGGER.trace("Found shadow is ok, returning null");
continue;
}
if (repoShadowType.getFailedOperationType() != null && FailedOperationTypeType.ADD != repoShadowType.getFailedOperationType()){
continue;
}
conflictingShadows.add(shadow);
}
}
if (conflictingShadows.isEmpty()){
return null;
}
if (conflictingShadows.size() > 1) {
for (PrismObject<ShadowType> result : conflictingShadows) {
LOGGER.trace("Search result:\n{}", result.debugDump());
}
LOGGER.error("More than one shadow found for " + secondaryIdentifiers);
if (LOGGER.isDebugEnabled()) {
for (PrismObject<ShadowType> conflictingShadow: conflictingShadows) {
LOGGER.debug("Conflicting shadow:\n{}", conflictingShadow.debugDump());
}
}
// TODO: Better error handling later
throw new IllegalStateException("More than one shadows found for " + secondaryIdentifiers);
}
PrismObject<ShadowType> shadow = conflictingShadows.get(0);
checkConsistency(shadow);
return shadow;
}
public PrismObject<ShadowType> lookupShadowBySecondaryIdentifiers(
ProvisioningContext ctx, Collection<ResourceAttribute<?>> secondaryIdentifiers, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
List<PrismObject<ShadowType>> shadows = lookupShadowsBySecondaryIdentifiers(ctx, secondaryIdentifiers, parentResult);
if (shadows == null || shadows.isEmpty()) {
return null;
}
if (shadows.size() > 1) {
LOGGER.error("Too many shadows ({}) for secondary identifiers {}: ", shadows.size(), secondaryIdentifiers,
shadows);
throw new ConfigurationException("Too many shadows ("+shadows.size()+") for secondary identifiers "+secondaryIdentifiers);
}
return shadows.get(0);
}
private List<PrismObject<ShadowType>> lookupShadowsBySecondaryIdentifiers(
ProvisioningContext ctx, Collection<ResourceAttribute<?>> secondaryIdentifiers, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
if (secondaryIdentifiers.size() < 1){
LOGGER.trace("Shadow does not contain secondary identifier. Skipping lookup shadows according to name.");
return null;
}
S_FilterEntry q = QueryBuilder.queryFor(ShadowType.class, prismContext)
.block();
for (ResourceAttribute<?> secondaryIdentifier : secondaryIdentifiers) {
// There may be identifiers that come from associations and they will have parent set to association/identifiers
// For the search to succeed we need all attribute to have "attributes" parent path.
secondaryIdentifier = ShadowUtil.fixAttributePath(secondaryIdentifier);
q = q.item(secondaryIdentifier.getPath(), secondaryIdentifier.getDefinition())
.eq(getNormalizedValue(secondaryIdentifier, ctx.getObjectClassDefinition()))
.or();
}
ObjectQuery query = q.none().endBlock()
.and().item(ShadowType.F_RESOURCE_REF).ref(ctx.getResourceOid())
.build();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Searching for shadow using filter on secondary identifier:\n{}",
query.debugDump());
}
// TODO: check for errors
List<PrismObject<ShadowType>> results = repositoryService.searchObjects(ShadowType.class, query, null, parentResult);
MiscSchemaUtil.reduceSearchResult(results);
LOGGER.trace("lookupShadow found {} objects", results.size());
if (LOGGER.isTraceEnabled() && results.size() == 1) {
LOGGER.trace("lookupShadow found\n{}", results.get(0).debugDump(1));
}
return results;
}
private void checkConsistency(PrismObject<ShadowType> shadow) {
ProvisioningUtil.checkShadowActivationConsistency(shadow);
}
private <T> ObjectFilter createAttributeEqualFilter(ProvisioningContext ctx,
ResourceAttribute<T> secondaryIdentifier) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.item(secondaryIdentifier.getPath(), secondaryIdentifier.getDefinition())
.eq(getNormalizedValue(secondaryIdentifier, ctx.getObjectClassDefinition()))
.buildFilter();
}
private <T> List<PrismPropertyValue<T>> getNormalizedValue(PrismProperty<T> attr, RefinedObjectClassDefinition rObjClassDef) throws SchemaException {
RefinedAttributeDefinition<T> refinedAttributeDefinition = rObjClassDef.findAttributeDefinition(attr.getElementName());
QName matchingRuleQName = refinedAttributeDefinition.getMatchingRuleQName();
MatchingRule<T> matchingRule = matchingRuleRegistry.getMatchingRule(matchingRuleQName, refinedAttributeDefinition.getTypeName());
List<PrismPropertyValue<T>> normalized = new ArrayList<>();
for (PrismPropertyValue<T> origPValue : attr.getValues()){
T normalizedValue = matchingRule.normalize(origPValue.getValue());
PrismPropertyValue<T> normalizedPValue = origPValue.clone();
normalizedPValue.setValue(normalizedValue);
normalized.add(normalizedPValue);
}
return normalized;
}
// beware, may return null if an shadow that was to be marked as DEAD, was deleted in the meantime
public PrismObject<ShadowType> findOrAddShadowFromChange(ProvisioningContext ctx, Change change,
OperationResult parentResult) throws SchemaException, CommunicationException,
ConfigurationException, SecurityViolationException, ObjectNotFoundException {
// Try to locate existing shadow in the repository
List<PrismObject<ShadowType>> accountList = searchShadowByIdenifiers(ctx, change, parentResult);
if (accountList.size() > 1) {
String message = "Found more than one shadow with the identifier " + change.getIdentifiers() + ".";
LOGGER.error(message);
parentResult.recordFatalError(message);
throw new IllegalArgumentException(message);
}
PrismObject<ShadowType> newShadow = null;
if (accountList.isEmpty()) {
// account was not found in the repository, create it now
if (change.getObjectDelta() == null || change.getObjectDelta().getChangeType() != ChangeType.DELETE) {
newShadow = createNewShadowFromChange(ctx, change, parentResult);
try {
ConstraintsChecker.onShadowAddOperation(newShadow.asObjectable());
String oid = repositoryService.addObject(newShadow, null, parentResult);
newShadow.setOid(oid);
if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) {
change.getObjectDelta().setOid(oid);
}
} catch (ObjectAlreadyExistsException e) {
parentResult.recordFatalError("Can't add " + SchemaDebugUtil.prettyPrint(newShadow)
+ " to the repository. Reason: " + e.getMessage(), e);
throw new IllegalStateException(e.getMessage(), e);
}
LOGGER.debug("Added new shadow (from change): {}", newShadow);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Added new shadow (from change):\n{}", newShadow.debugDump());
}
}
} else {
// Account was found in repository
newShadow = accountList.get(0);
if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE) {
Collection<? extends ItemDelta> deadDeltas = PropertyDelta
.createModificationReplacePropertyCollection(ShadowType.F_DEAD,
newShadow.getDefinition(), true);
try {
ConstraintsChecker.onShadowModifyOperation(deadDeltas);
repositoryService.modifyObject(ShadowType.class, newShadow.getOid(), deadDeltas,
parentResult);
} catch (ObjectAlreadyExistsException e) {
parentResult.recordFatalError(
"Can't add " + SchemaDebugUtil.prettyPrint(newShadow)
+ " to the repository. Reason: " + e.getMessage(), e);
throw new IllegalStateException(e.getMessage(), e);
} catch (ObjectNotFoundException e) {
parentResult.recordWarning("Shadow " + SchemaDebugUtil.prettyPrint(newShadow)
+ " was probably deleted from the repository in the meantime. Exception: "
+ e.getMessage(), e);
return null;
}
}
}
return newShadow;
}
public PrismObject<ShadowType> findOrAddShadowFromChangeGlobalContext(ProvisioningContext globalCtx, Change change,
OperationResult parentResult) throws SchemaException, CommunicationException,
ConfigurationException, SecurityViolationException, ObjectNotFoundException {
// Try to locate existing shadow in the repository
List<PrismObject<ShadowType>> accountList = searchShadowByIdenifiers(globalCtx, change, parentResult);
if (accountList.size() > 1) {
String message = "Found more than one shadow with the identifier " + change.getIdentifiers() + ".";
LOGGER.error(message);
parentResult.recordFatalError(message);
throw new IllegalArgumentException(message);
}
PrismObject<ShadowType> newShadow = null;
if (accountList.isEmpty()) {
// account was not found in the repository, create it now
if (change.getObjectDelta() == null || change.getObjectDelta().getChangeType() != ChangeType.DELETE) {
newShadow = createNewShadowFromChange(globalCtx, change, parentResult);
try {
ConstraintsChecker.onShadowAddOperation(newShadow.asObjectable());
String oid = repositoryService.addObject(newShadow, null, parentResult);
newShadow.setOid(oid);
if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) {
change.getObjectDelta().setOid(oid);
}
} catch (ObjectAlreadyExistsException e) {
parentResult.recordFatalError("Can't add " + SchemaDebugUtil.prettyPrint(newShadow)
+ " to the repository. Reason: " + e.getMessage(), e);
throw new IllegalStateException(e.getMessage(), e);
}
LOGGER.debug("Added new shadow (from global change): {}", newShadow);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Added new shadow (from global change):\n{}", newShadow.debugDump());
}
}
} else {
// Account was found in repository
newShadow = accountList.get(0);
if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE) {
Collection<? extends ItemDelta> deadDeltas = PropertyDelta
.createModificationReplacePropertyCollection(ShadowType.F_DEAD,
newShadow.getDefinition(), true);
try {
ConstraintsChecker.onShadowModifyOperation(deadDeltas);
repositoryService.modifyObject(ShadowType.class, newShadow.getOid(), deadDeltas,
parentResult);
} catch (ObjectAlreadyExistsException e) {
parentResult.recordFatalError(
"Can't add " + SchemaDebugUtil.prettyPrint(newShadow)
+ " to the repository. Reason: " + e.getMessage(), e);
throw new IllegalStateException(e.getMessage(), e);
} catch (ObjectNotFoundException e) {
parentResult.recordWarning("Shadow " + SchemaDebugUtil.prettyPrint(newShadow)
+ " was probably deleted from the repository in the meantime. Exception: "
+ e.getMessage(), e);
return null;
}
}
}
return newShadow;
}
private PrismObject<ShadowType> createNewShadowFromChange(ProvisioningContext ctx, Change change,
OperationResult parentResult) throws SchemaException,
CommunicationException, ConfigurationException,
SecurityViolationException, ObjectNotFoundException {
PrismObject<ShadowType> shadow = change.getCurrentShadow();
if (shadow == null){
//try to look in the delta, if there exists some account to be added
if (change.getObjectDelta() != null && change.getObjectDelta().isAdd()){
shadow = (PrismObject<ShadowType>) change.getObjectDelta().getObjectToAdd();
}
}
if (shadow == null){
throw new IllegalStateException("Could not create shadow from change description. Neither current shadow, nor delta containing shadow exits.");
}
try {
shadow = createRepositoryShadow(ctx, shadow);
} catch (SchemaException ex) {
parentResult.recordFatalError("Can't create shadow from identifiers: "
+ change.getIdentifiers());
throw new SchemaException("Can't create shadow from identifiers: "
+ change.getIdentifiers());
}
parentResult.recordSuccess();
return shadow;
}
private List<PrismObject<ShadowType>> searchShadowByIdenifiers(ProvisioningContext ctx, Change change, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
Collection<ResourceAttribute<?>> identifiers = change.getIdentifiers();
ObjectQuery query = createSearchShadowQuery(ctx, identifiers, true, prismContext, parentResult);
List<PrismObject<ShadowType>> accountList = null;
try {
accountList = repositoryService.searchObjects(ShadowType.class, query, null, parentResult);
} catch (SchemaException ex) {
parentResult.recordFatalError(
"Failed to search shadow according to the identifiers: " + identifiers + ". Reason: "
+ ex.getMessage(), ex);
throw new SchemaException("Failed to search shadow according to the identifiers: "
+ identifiers + ". Reason: " + ex.getMessage(), ex);
}
MiscSchemaUtil.reduceSearchResult(accountList);
return accountList;
}
private ObjectQuery createSearchShadowQuery(ProvisioningContext ctx, Collection<ResourceAttribute<?>> identifiers, boolean primaryIdentifiersOnly,
PrismContext prismContext, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
S_AtomicFilterEntry q = QueryBuilder.queryFor(ShadowType.class, prismContext);
RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();
for (PrismProperty<?> identifier : identifiers) {
RefinedAttributeDefinition rAttrDef;
PrismPropertyValue<?> identifierValue = identifier.getValue();
if (objectClassDefinition == null) {
// If there is no specific object class definition then the identifier definition
// must be the same in all object classes and that means that we can use
// definition from any of them.
RefinedObjectClassDefinition anyDefinition = ctx.getRefinedSchema().getRefinedDefinitions().iterator().next();
rAttrDef = anyDefinition.findAttributeDefinition(identifier.getElementName());
if (primaryIdentifiersOnly && !anyDefinition.isPrimaryIdentifier(identifier.getElementName())) {
continue;
}
} else {
if (primaryIdentifiersOnly && !objectClassDefinition.isPrimaryIdentifier(identifier.getElementName())) {
continue;
}
rAttrDef = objectClassDefinition.findAttributeDefinition(identifier.getElementName());
}
String normalizedIdentifierValue = (String) getNormalizedAttributeValue(identifierValue, rAttrDef);
PrismPropertyDefinition<String> def = (PrismPropertyDefinition<String>) identifier.getDefinition();
q = q.itemWithDef(def, ShadowType.F_ATTRIBUTES, def.getName()).eq(normalizedIdentifierValue).and();
}
if (identifiers.size() < 1) {
throw new SchemaException("Identifier not specified. Cannot create search query by identifier.");
}
if (objectClassDefinition != null) {
q = q.item(ShadowType.F_OBJECT_CLASS).eq(objectClassDefinition.getTypeName()).and();
}
return q.item(ShadowType.F_RESOURCE_REF).ref(ctx.getResourceOid()).build();
}
private ObjectQuery createSearchShadowQuery(ProvisioningContext ctx, PrismObject<ShadowType> resourceShadow,
PrismContext prismContext, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(resourceShadow);
PrismProperty identifier = attributesContainer.getPrimaryIdentifier();
Collection<PrismPropertyValue<Object>> idValues = identifier.getValues();
// Only one value is supported for an identifier
if (idValues.size() > 1) {
// TODO: This should probably be switched to checked exception later
throw new IllegalArgumentException("More than one identifier value is not supported");
}
if (idValues.size() < 1) {
// TODO: This should probably be switched to checked exception later
throw new IllegalArgumentException("The identifier has no value");
}
// We have all the data, we can construct the filter now
try {
// TODO TODO TODO TODO: set matching rule instead of null
PrismPropertyDefinition def = identifier.getDefinition();
return QueryBuilder.queryFor(ShadowType.class, prismContext)
.itemWithDef(def, ShadowType.F_ATTRIBUTES, def.getName()).eq(getNormalizedValue(identifier, ctx.getObjectClassDefinition()))
.and().item(ShadowType.F_OBJECT_CLASS).eq(resourceShadow.getPropertyRealValue(ShadowType.F_OBJECT_CLASS, QName.class))
.and().item(ShadowType.F_RESOURCE_REF).ref(ctx.getResourceOid())
.build();
} catch (SchemaException e) {
throw new SchemaException("Schema error while creating search filter: " + e.getMessage(), e);
}
}
public SearchResultMetadata searchObjectsIterativeRepository(
ProvisioningContext ctx, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options,
com.evolveum.midpoint.schema.ResultHandler<ShadowType> repoHandler, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
ObjectQuery repoQuery = query.clone();
processQueryMatchingRules(repoQuery, ctx.getObjectClassDefinition());
return repositoryService.searchObjectsIterative(ShadowType.class, repoQuery, repoHandler, options, false, parentResult); // TODO think about strictSequential flag
}
/**
* Visit the query and normalize values (or set matching rules) as needed
*/
private void processQueryMatchingRules(ObjectQuery repoQuery, final RefinedObjectClassDefinition objectClassDef) {
ObjectFilter filter = repoQuery.getFilter();
Visitor visitor = f -> {
try {
processQueryMatchingRuleFilter(f, objectClassDef);
} catch (SchemaException e) {
throw new SystemException(e);
}
};
filter.accept(visitor);
}
private <T> void processQueryMatchingRuleFilter(ObjectFilter filter, RefinedObjectClassDefinition objectClassDef) throws SchemaException {
if (!(filter instanceof EqualFilter)) {
return;
}
EqualFilter<T> eqFilter = (EqualFilter)filter;
ItemPath parentPath = eqFilter.getParentPath();
if (parentPath == null || !parentPath.equivalent(SchemaConstants.PATH_ATTRIBUTES)) {
return;
}
QName attrName = eqFilter.getElementName();
RefinedAttributeDefinition rAttrDef = objectClassDef.findAttributeDefinition(attrName);
if (rAttrDef == null) {
throw new SchemaException("Unknown attribute "+attrName+" in filter "+filter);
}
QName matchingRuleQName = rAttrDef.getMatchingRuleQName();
if (matchingRuleQName == null) {
return;
}
Class<?> valueClass = null;
MatchingRule<T> matchingRule = matchingRuleRegistry.getMatchingRule(matchingRuleQName, rAttrDef.getTypeName());
List<PrismValue> newValues = new ArrayList<PrismValue>();
if (eqFilter.getValues() != null) {
for (PrismPropertyValue<T> ppval : eqFilter.getValues()) {
T normalizedRealValue = matchingRule.normalize(ppval.getValue());
PrismPropertyValue<T> newPPval = ppval.clone();
newPPval.setValue(normalizedRealValue);
newValues.add(newPPval);
if (normalizedRealValue != null) {
valueClass = normalizedRealValue.getClass();
}
}
eqFilter.getValues().clear();
eqFilter.getValues().addAll((Collection) newValues);
LOGGER.trace("Replacing values for attribute {} in search filter with normalized values because there is a matching rule, normalized values: {}",
attrName, newValues);
}
if (eqFilter.getMatchingRule() == null) {
QName supportedMatchingRule = valueClass != null ?
repositoryService.getApproximateSupportedMatchingRule(valueClass, matchingRuleQName) : matchingRuleQName;
eqFilter.setMatchingRule(supportedMatchingRule);
LOGGER.trace("Setting matching rule to {} (supported by repo as a replacement for {} to search for {})",
supportedMatchingRule, matchingRuleQName, valueClass);
}
}
// Used when new resource object is discovered
public PrismObject<ShadowType> addDiscoveredRepositoryShadow(ProvisioningContext ctx,
PrismObject<ShadowType> resourceShadow, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ObjectAlreadyExistsException {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Adding new shadow from resource object: {}", resourceShadow.debugDump());
}
PrismObject<ShadowType> repoShadow = createRepositoryShadow(ctx, resourceShadow);
ConstraintsChecker.onShadowAddOperation(repoShadow.asObjectable());
String oid = repositoryService.addObject(repoShadow, null, parentResult);
repoShadow.setOid(oid);
LOGGER.debug("Added new shadow (from resource object): {}", repoShadow);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Added new shadow (from resource object):\n{}", repoShadow.debugDump());
}
return repoShadow;
}
// Used after ADD operation on resource
public String addNewRepositoryShadow(ProvisioningContext ctx, AsynchronousOperationReturnValue<PrismObject<ShadowType>> addResult, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ObjectAlreadyExistsException {
PrismObject<ShadowType> resourceShadow = addResult.getReturnValue();
PrismObject<ShadowType> repoShadow = createRepositoryShadow(ctx, resourceShadow);
if (repoShadow == null) {
parentResult
.recordFatalError("Error while creating account shadow object to save in the reposiotory. Shadow is null.");
throw new IllegalStateException(
"Error while creating account shadow object to save in the reposiotory. Shadow is null.");
}
addPendingOperationAdd(repoShadow, addResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Adding repository shadow\n{}", repoShadow.debugDump());
}
String oid = null;
try {
ConstraintsChecker.onShadowAddOperation(repoShadow.asObjectable());
oid = repositoryService.addObject(repoShadow, null, parentResult);
} catch (ObjectAlreadyExistsException ex) {
// This should not happen. The OID is not supplied and it is
// generated by the repo
// If it happens, it must be a repo bug. Therefore it is safe to
// convert to runtime exception
parentResult.recordFatalError(
"Couldn't add shadow object to the repository. Shadow object already exist. Reason: "
+ ex.getMessage(), ex);
throw new ObjectAlreadyExistsException(
"Couldn't add shadow object to the repository. Shadow object already exist. Reason: "
+ ex.getMessage(), ex);
}
repoShadow.setOid(oid);
LOGGER.trace("Object added to the repository successfully.");
parentResult.recordSuccess();
return repoShadow.getOid();
}
private void addPendingOperationAdd(PrismObject<ShadowType> repoShadow,
AsynchronousOperationReturnValue<PrismObject<ShadowType>> addResult) throws SchemaException {
if (!addResult.isInProgress()) {
return;
}
PrismObject<ShadowType> resourceShadow = addResult.getReturnValue();
ShadowType repoShadowType = repoShadow.asObjectable();
ObjectDelta<ShadowType> addDelta = resourceShadow.createAddDelta();
ObjectDeltaType addDeltaType = DeltaConvertor.toObjectDeltaType(addDelta);
PendingOperationType pendingOperation = new PendingOperationType();
pendingOperation.setDelta(addDeltaType);
pendingOperation.setRequestTimestamp(clock.currentTimeXMLGregorianCalendar());
pendingOperation.setResultStatus(OperationResultStatusType.IN_PROGRESS);
pendingOperation.setAsynchronousOperationReference(addResult.getOperationResult().getAsynchronousOperationReference());
repoShadowType.getPendingOperation().add(pendingOperation);
repoShadowType.setExists(false);
}
private void addPendingOperationModify(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Collection<? extends ItemDelta> pendingModifications,
OperationResult resourceOperationResult, OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
ObjectDelta<ShadowType> pendingDelta = shadow.createModifyDelta();
for (ItemDelta pendingModification: pendingModifications) {
pendingDelta.addModification(pendingModification.clone());
}
addPendingOperationDelta(ctx, shadow, pendingDelta, resourceOperationResult, parentResult);
}
private void addPendingOperationDelete(ProvisioningContext ctx, PrismObject<ShadowType> oldRepoShadow,
OperationResult resourceOperationResult, OperationResult parentResult) throws SchemaException, ObjectNotFoundException {
ObjectDelta<ShadowType> pendingDelta = oldRepoShadow.createDeleteDelta();
addPendingOperationDelta(ctx, oldRepoShadow, pendingDelta, resourceOperationResult, parentResult);
}
private void addPendingOperationDelta(ProvisioningContext ctx, PrismObject<ShadowType> shadow, ObjectDelta<ShadowType> pendingDelta,
OperationResult resourceOperationResult, OperationResult parentResult) throws SchemaException, ObjectNotFoundException {
ObjectDeltaType pendingDeltaType = DeltaConvertor.toObjectDeltaType(pendingDelta);
PendingOperationType pendingOperation = new PendingOperationType();
pendingOperation.setDelta(pendingDeltaType);
pendingOperation.setRequestTimestamp(clock.currentTimeXMLGregorianCalendar());
pendingOperation.setResultStatus(OperationResultStatusType.IN_PROGRESS);
pendingOperation.setAsynchronousOperationReference(resourceOperationResult.getAsynchronousOperationReference());
Collection repoDeltas = new ArrayList<>(1);
ContainerDelta<PendingOperationType> cdelta = ContainerDelta.createDelta(ShadowType.F_PENDING_OPERATION, shadow.getDefinition());
cdelta.addValuesToAdd(pendingOperation.asPrismContainerValue());
repoDeltas.add(cdelta);
try {
repositoryService.modifyObject(ShadowType.class, shadow.getOid(), repoDeltas, parentResult);
} catch (ObjectAlreadyExistsException ex) {
throw new SystemException(ex);
}
}
/**
* Create a copy of a shadow that is suitable for repository storage.
*/
private PrismObject<ShadowType> createRepositoryShadow(ProvisioningContext ctx, PrismObject<ShadowType> shadow)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(shadow);
PrismObject<ShadowType> repoShadow = shadow.clone();
ShadowType repoShadowType = repoShadow.asObjectable();
ResourceAttributeContainer repoAttributesContainer = ShadowUtil
.getAttributesContainer(repoShadow);
CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx);
if (cachingStrategy == CachingStategyType.NONE) {
// Clean all repoShadow attributes and add only those that should be
// there
repoAttributesContainer.clear();
Collection<ResourceAttribute<?>> primaryIdentifiers = attributesContainer.getPrimaryIdentifiers();
for (PrismProperty<?> p : primaryIdentifiers) {
repoAttributesContainer.add(p.clone());
}
Collection<ResourceAttribute<?>> secondaryIdentifiers = attributesContainer.getSecondaryIdentifiers();
for (PrismProperty<?> p : secondaryIdentifiers) {
repoAttributesContainer.add(p.clone());
}
// Also add all the attributes that act as association identifiers.
// We will need them when the shadow is deleted (to remove the shadow from entitlements).
RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();
for (RefinedAssociationDefinition associationDef: objectClassDefinition.getAssociationDefinitions()) {
if (associationDef.getResourceObjectAssociationType().getDirection() == ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) {
QName valueAttributeName = associationDef.getResourceObjectAssociationType().getValueAttribute();
if (repoAttributesContainer.findAttribute(valueAttributeName) == null) {
ResourceAttribute<Object> valueAttribute = attributesContainer.findAttribute(valueAttributeName);
if (valueAttribute != null) {
repoAttributesContainer.add(valueAttribute.clone());
}
}
}
}
repoShadowType.setCachingMetadata(null);
ProvisioningUtil.cleanupShadowActivation(repoShadowType);
} else if (cachingStrategy == CachingStategyType.PASSIVE) {
// Do not need to clear anything. Just store all attributes and add metadata.
CachingMetadataType cachingMetadata = new CachingMetadataType();
cachingMetadata.setRetrievalTimestamp(clock.currentTimeXMLGregorianCalendar());
repoShadowType.setCachingMetadata(cachingMetadata);
} else {
throw new ConfigurationException("Unknown caching strategy "+cachingStrategy);
}
setKindIfNecessary(repoShadowType, ctx.getObjectClassDefinition());
// setIntentIfNecessary(repoShadowType, objectClassDefinition);
// Store only password meta-data in repo
CredentialsType creds = repoShadowType.getCredentials();
if (creds != null) {
PasswordType passwordType = creds.getPassword();
if (passwordType != null) {
ProvisioningUtil.cleanupShadowPassword(passwordType);
PrismObject<UserType> owner = null;
if (ctx.getTask() != null) {
owner = ctx.getTask().getOwner();
}
ProvisioningUtil.addPasswordMetadata(passwordType, clock.currentTimeXMLGregorianCalendar(), owner);
}
// TODO: other credential types - later
}
// additional check if the shadow doesn't contain resource, if yes,
// convert to the resource reference.
if (repoShadowType.getResource() != null) {
repoShadowType.setResource(null);
repoShadowType.setResourceRef(ObjectTypeUtil.createObjectRef(ctx.getResource()));
}
// if shadow does not contain resource or resource reference, create it
// now
if (repoShadowType.getResourceRef() == null) {
repoShadowType.setResourceRef(ObjectTypeUtil.createObjectRef(ctx.getResource()));
}
if (repoShadowType.getName() == null) {
repoShadowType.setName(new PolyStringType(ShadowUtil.determineShadowName(shadow)));
}
if (repoShadowType.getObjectClass() == null) {
repoShadowType.setObjectClass(attributesContainer.getDefinition().getTypeName());
}
if (repoShadowType.isProtectedObject() != null){
repoShadowType.setProtectedObject(null);
}
normalizeAttributes(repoShadow, ctx.getObjectClassDefinition());
return repoShadow;
}
public void modifyShadow(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Collection<? extends ItemDelta> modifications,
OperationResult resourceOperationResult, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException {
LOGGER.trace("Updating repository shadow, resourceOperationResult={}, {} modifications", resourceOperationResult.getStatus(), modifications.size());
if (resourceOperationResult.isInProgress()) {
addPendingOperationModify(ctx, shadow, modifications, resourceOperationResult, parentResult);
} else {
modifyShadowAttributes(ctx, shadow, modifications, parentResult);
}
}
/**
* Really modifies shadow attributes. It applies the changes. It is used for synchronous operations and also for
* applying the results of completed asynchronous operations.
*/
public void modifyShadowAttributes(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Collection<? extends ItemDelta> modifications,
OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException {
Collection<? extends ItemDelta> shadowChanges = extractRepoShadowChanges(ctx, shadow, modifications);
if (shadowChanges != null && !shadowChanges.isEmpty()) {
LOGGER.trace(
"Detected shadow changes. Start to modify shadow in the repository, applying modifications {}",
DebugUtil.debugDump(shadowChanges));
try {
ConstraintsChecker.onShadowModifyOperation(shadowChanges);
repositoryService.modifyObject(ShadowType.class, shadow.getOid(), shadowChanges, parentResult);
LOGGER.trace("Shadow changes processed successfully.");
} catch (ObjectAlreadyExistsException ex) {
throw new SystemException(ex);
}
}
}
@SuppressWarnings("rawtypes")
private Collection<? extends ItemDelta> extractRepoShadowChanges(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Collection<? extends ItemDelta> objectChange)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();
CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx);
Collection<ItemDelta> repoChanges = new ArrayList<ItemDelta>();
for (ItemDelta itemDelta : objectChange) {
if (new ItemPath(ShadowType.F_ATTRIBUTES).equivalent(itemDelta.getParentPath())) {
QName attrName = itemDelta.getElementName();
if (objectClassDefinition.isSecondaryIdentifier(attrName)) {
// Change of secondary identifier means object rename. We also need to change $shadow/name
// TODO: change this to displayName attribute later
String newName = null;
if (itemDelta.getValuesToReplace() != null && !itemDelta.getValuesToReplace().isEmpty()) {
newName = ((PrismPropertyValue)itemDelta.getValuesToReplace().iterator().next()).getValue().toString();
} else if (itemDelta.getValuesToAdd() != null && !itemDelta.getValuesToAdd().isEmpty()) {
newName = ((PrismPropertyValue)itemDelta.getValuesToAdd().iterator().next()).getValue().toString();
}
PropertyDelta<PolyString> nameDelta = PropertyDelta.createReplaceDelta(shadow.getDefinition(), ShadowType.F_NAME, new PolyString(newName));
repoChanges.add(nameDelta);
}
if (!ProvisioningUtil.shouldStoreAtributeInShadow(objectClassDefinition, attrName, cachingStrategy)) {
continue;
}
} else if (new ItemPath(ShadowType.F_ACTIVATION).equivalent(itemDelta.getParentPath())) {
if (!ProvisioningUtil.shouldStoreActivationItemInShadow(itemDelta.getElementName(), cachingStrategy)) {
continue;
}
} else if (new ItemPath(ShadowType.F_ACTIVATION).equivalent(itemDelta.getPath())) { // should not occur, but for completeness...
for (PrismContainerValue<ActivationType> valueToAdd : ((ContainerDelta<ActivationType>) itemDelta).getValuesToAdd()) {
ProvisioningUtil.cleanupShadowActivation(valueToAdd.asContainerable());
}
for (PrismContainerValue<ActivationType> valueToReplace : ((ContainerDelta<ActivationType>) itemDelta).getValuesToReplace()) {
ProvisioningUtil.cleanupShadowActivation(valueToReplace.asContainerable());
}
} else if (SchemaConstants.PATH_PASSWORD.equivalent(itemDelta.getParentPath())) {
continue;
}
normalizeDelta(itemDelta, objectClassDefinition);
repoChanges.add(itemDelta);
}
return repoChanges;
}
@SuppressWarnings("unchecked")
public Collection<ItemDelta> updateShadow(ProvisioningContext ctx, PrismObject<ShadowType> resourceShadow,
Collection<? extends ItemDelta> aprioriDeltas, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
PrismObject<ShadowType> repoShadow = repositoryService.getObject(ShadowType.class, resourceShadow.getOid(), null, result);
RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();
Collection<ItemDelta> repoShadowChanges = new ArrayList<ItemDelta>();
CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx);
for (RefinedAttributeDefinition attrDef: objectClassDefinition.getAttributeDefinitions()) {
if (ProvisioningUtil.shouldStoreAtributeInShadow(objectClassDefinition, attrDef.getName(), cachingStrategy)) {
ResourceAttribute<Object> resourceAttr = ShadowUtil.getAttribute(resourceShadow, attrDef.getName());
PrismProperty<Object> repoAttr = repoShadow.findProperty(new ItemPath(ShadowType.F_ATTRIBUTES, attrDef.getName()));
PropertyDelta attrDelta;
if (repoAttr == null && repoAttr == null) {
continue;
}
if (repoAttr == null) {
attrDelta = attrDef.createEmptyDelta(new ItemPath(ShadowType.F_ATTRIBUTES, attrDef.getName()));
attrDelta.setValuesToReplace(PrismValue.cloneCollection(resourceAttr.getValues()));
} else {
attrDelta = repoAttr.diff(resourceAttr);
// LOGGER.trace("DIFF:\n{}\n-\n{}\n=:\n{}", repoAttr==null?null:repoAttr.debugDump(1), resourceAttr==null?null:resourceAttr.debugDump(1), attrDelta==null?null:attrDelta.debugDump(1));
}
if (attrDelta != null && !attrDelta.isEmpty()) {
normalizeDelta(attrDelta, attrDef);
repoShadowChanges.add(attrDelta);
}
}
}
// TODO: reflect activation updates on cached shadow
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Updating repo shadow {}:\n{}", resourceShadow.getOid(), DebugUtil.debugDump(repoShadowChanges));
}
try {
repositoryService.modifyObject(ShadowType.class, resourceShadow.getOid(), repoShadowChanges, result);
} catch (ObjectAlreadyExistsException e) {
// We are not renaming the object here. This should not happen.
throw new SystemException(e.getMessage(), e);
}
return repoShadowChanges;
}
/**
* Updates repository shadow based on shadow from resource. Handles rename cases,
* change of auxiliary object classes, etc.
* @returns repository shadow as it should look like after the update
*/
@SuppressWarnings("unchecked")
public PrismObject<ShadowType> updateShadow(ProvisioningContext ctx, PrismObject<ShadowType> currentResourceShadow,
PrismObject<ShadowType> oldRepoShadow, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, CommunicationException {
RefinedObjectClassDefinition ocDef = ctx.computeCompositeObjectClassDefinition(currentResourceShadow);
ObjectDelta<ShadowType> shadowDelta = oldRepoShadow.createModifyDelta();
PrismContainer<Containerable> currentResourceAttributesContainer = currentResourceShadow.findContainer(ShadowType.F_ATTRIBUTES);
PrismContainer<Containerable> oldRepoAttributesContainer = oldRepoShadow.findContainer(ShadowType.F_ATTRIBUTES);
ShadowType oldRepoShadowType = oldRepoShadow.asObjectable();
ShadowType currentResourceShadowType = currentResourceShadow.asObjectable();
if (oldRepoShadowType.isExists() != currentResourceShadowType.isExists()) {
// Resource object obviously exists when we have got here
shadowDelta.addModificationReplaceProperty(ShadowType.F_EXISTS, currentResourceShadowType.isExists());
}
CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx);
for (Item<?, ?> currentResourceItem: currentResourceAttributesContainer.getValue().getItems()) {
if (currentResourceItem instanceof PrismProperty<?>) {
PrismProperty<?> currentResourceAttrProperty = (PrismProperty<?>)currentResourceItem;
RefinedAttributeDefinition<Object> attrDef = ocDef.findAttributeDefinition(currentResourceAttrProperty.getElementName());
if (ProvisioningUtil.shouldStoreAtributeInShadow(ocDef, attrDef.getName(), cachingStrategy)) {
MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(attrDef.getMatchingRuleQName(), attrDef.getTypeName());
PrismProperty<Object> oldRepoAttributeProperty = oldRepoAttributesContainer.findProperty(currentResourceAttrProperty.getElementName());
if (oldRepoAttributeProperty == null ) {
PropertyDelta<?> attrAddDelta = currentResourceAttrProperty.createDelta();
for (PrismPropertyValue pval: currentResourceAttrProperty.getValues()) {
Object normalizedRealValue;
if (matchingRule == null) {
normalizedRealValue = pval.getValue();
} else {
normalizedRealValue = matchingRule.normalize(pval.getValue());
}
attrAddDelta.addValueToAdd(new PrismPropertyValue(normalizedRealValue));
LOGGER.trace("CURRENT ATTR:\n{}\nATTR DELTA:\n{}", currentResourceAttrProperty.debugDump(1), attrAddDelta.debugDump(1));
}
shadowDelta.addModification(attrAddDelta);
} else {
if (attrDef.isSingleValue()) {
Object currentResourceRealValue = currentResourceAttrProperty.getRealValue();
Object currentResourceNormalizedRealValue;
if (matchingRule == null) {
currentResourceNormalizedRealValue = currentResourceRealValue;
} else {
currentResourceNormalizedRealValue = matchingRule.normalize(currentResourceRealValue);
}
if (!currentResourceNormalizedRealValue.equals(oldRepoAttributeProperty.getRealValue())) {
LOGGER.trace("CURRENT ATTR:\n{}\ncurrentResourceNormalizedRealValue: {}", currentResourceAttrProperty.debugDump(1), currentResourceNormalizedRealValue);
shadowDelta.addModificationReplaceProperty(currentResourceAttrProperty.getPath(), currentResourceNormalizedRealValue);
}
} else {
PrismProperty<Object> normalizedCurrentResourceAttrProperty = (PrismProperty<Object>) currentResourceAttrProperty.clone();
if (matchingRule != null) {
for (PrismPropertyValue pval: normalizedCurrentResourceAttrProperty.getValues()) {
Object normalizedRealValue = matchingRule.normalize(pval.getValue());
pval.setValue(normalizedRealValue);
}
}
PropertyDelta<Object> attrDiff = oldRepoAttributeProperty.diff(normalizedCurrentResourceAttrProperty);
LOGGER.trace("DIFF:\n{}\n-\n{}\n=:\n{}",
oldRepoAttributeProperty==null?null:oldRepoAttributeProperty.debugDump(1),
normalizedCurrentResourceAttrProperty==null?null:normalizedCurrentResourceAttrProperty.debugDump(1),
attrDiff==null?null:attrDiff.debugDump(1));
if (attrDiff != null && !attrDiff.isEmpty()) {
attrDiff.setParentPath(new ItemPath(ShadowType.F_ATTRIBUTES));
shadowDelta.addModification(attrDiff);
}
}
}
}
}
}
for (Item<?, ?> oldRepoItem: oldRepoAttributesContainer.getValue().getItems()) {
if (oldRepoItem instanceof PrismProperty<?>) {
PrismProperty<?> oldRepoAttrProperty = (PrismProperty<?>)oldRepoItem;
RefinedAttributeDefinition<Object> attrDef = ocDef.findAttributeDefinition(oldRepoAttrProperty.getElementName());
PrismProperty<Object> currentAttribute = currentResourceAttributesContainer.findProperty(oldRepoAttrProperty.getElementName());
if (attrDef == null || !ProvisioningUtil.shouldStoreAtributeInShadow(ocDef, attrDef.getName(), cachingStrategy) ||
currentAttribute == null) {
// No definition for this property it should not be there or no current value: remove it from the shadow
PropertyDelta<?> oldRepoAttrPropDelta = oldRepoAttrProperty.createDelta();
oldRepoAttrPropDelta.addValuesToDelete((Collection)PrismPropertyValue.cloneCollection(oldRepoAttrProperty.getValues()));
shadowDelta.addModification(oldRepoAttrPropDelta);
}
}
}
PolyString currentShadowName = ShadowUtil.determineShadowName(currentResourceShadow);
PolyString oldRepoShadowName = oldRepoShadow.getName();
if (!currentShadowName.equalsOriginalValue(oldRepoShadowName)) {
PropertyDelta<?> shadowNameDelta = PropertyDelta.createModificationReplaceProperty(ShadowType.F_NAME,
oldRepoShadow.getDefinition(),currentShadowName);
shadowDelta.addModification(shadowNameDelta);
}
PropertyDelta<QName> auxOcDelta = (PropertyDelta)PrismProperty.diff(
oldRepoShadow.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS),
currentResourceShadow.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS));
if (auxOcDelta != null) {
shadowDelta.addModification(auxOcDelta);
}
if (cachingStrategy == CachingStategyType.NONE) {
if (oldRepoShadowType.getCachingMetadata() != null) {
shadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA);
}
} else if (cachingStrategy == CachingStategyType.PASSIVE) {
compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, currentResourceShadow, oldRepoShadow);
compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_FROM, currentResourceShadow, oldRepoShadow);
compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_TO, currentResourceShadow, oldRepoShadow);
compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, currentResourceShadow, oldRepoShadow);
CachingMetadataType cachingMetadata = new CachingMetadataType();
cachingMetadata.setRetrievalTimestamp(clock.currentTimeXMLGregorianCalendar());
shadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA, cachingMetadata);
} else {
throw new ConfigurationException("Unknown caching strategy "+cachingStrategy);
}
if (!shadowDelta.isEmpty()) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Updating repo shadow {} with delta:\n{}", oldRepoShadow, shadowDelta.debugDump(1));
}
ConstraintsChecker.onShadowModifyOperation(shadowDelta.getModifications());
try {
repositoryService.modifyObject(ShadowType.class, oldRepoShadow.getOid(), shadowDelta.getModifications(), parentResult);
} catch (ObjectAlreadyExistsException e) {
// This should not happen for shadows
throw new SystemException(e.getMessage(), e);
}
PrismObject<ShadowType> newRepoShadow = oldRepoShadow.clone();
shadowDelta.applyTo(newRepoShadow);
return newRepoShadow;
} else {
LOGGER.trace("No need to update repo shadow {} (empty delta)", oldRepoShadow);
return oldRepoShadow;
}
}
private <T> void compareUpdateProperty(ObjectDelta<ShadowType> shadowDelta,
ItemPath itemPath, PrismObject<ShadowType> currentResourceShadow, PrismObject<ShadowType> oldRepoShadow) {
PrismProperty<T> currentProperty = currentResourceShadow.findProperty(itemPath);
PrismProperty<T> oldProperty = oldRepoShadow.findProperty(itemPath);
PropertyDelta<T> itemDelta = PrismProperty.diff(oldProperty, currentProperty);
if (itemDelta != null && !itemDelta.isEmpty()) {
shadowDelta.addModification(itemDelta);
}
}
public void deleteShadow(ProvisioningContext ctx, PrismObject<ShadowType> oldRepoShadow, OperationResult resourceOperationResult, OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
LOGGER.trace("Deleting repository {}, resourceOperationResult={}", oldRepoShadow,
resourceOperationResult==null?null:resourceOperationResult.getStatus());
if (resourceOperationResult != null && resourceOperationResult.isInProgress()) {
addPendingOperationDelete(ctx, oldRepoShadow, resourceOperationResult, parentResult);
} else {
repositoryService.deleteObject(ShadowType.class, oldRepoShadow.getOid(), parentResult);
}
}
/**
* Re-reads the shadow, re-evaluates the identifiers and stored values, updates them if necessary. Returns
* fixed shadow.
*/
public PrismObject<ShadowType> fixShadow(ProvisioningContext ctx, PrismObject<ShadowType> origRepoShadow,
OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ConfigurationException, CommunicationException {
PrismObject<ShadowType> currentRepoShadow = repositoryService.getObject(ShadowType.class, origRepoShadow.getOid(), null, parentResult);
ProvisioningContext shadowCtx = ctx.spawn(currentRepoShadow);
RefinedObjectClassDefinition ocDef = shadowCtx.getObjectClassDefinition();
PrismContainer<Containerable> attributesContainer = currentRepoShadow.findContainer(ShadowType.F_ATTRIBUTES);
if (attributesContainer != null) {
ObjectDelta<ShadowType> shadowDelta = currentRepoShadow.createModifyDelta();
for (Item<?, ?> item: attributesContainer.getValue().getItems()) {
if (item instanceof PrismProperty<?>) {
PrismProperty<?> attrProperty = (PrismProperty<?>)item;
RefinedAttributeDefinition<Object> attrDef = ocDef.findAttributeDefinition(attrProperty.getElementName());
if (attrDef == null) {
// No definition for this property, it should not be in the shadow
PropertyDelta<?> oldRepoAttrPropDelta = attrProperty.createDelta();
oldRepoAttrPropDelta.addValuesToDelete((Collection)PrismPropertyValue.cloneCollection(attrProperty.getValues()));
shadowDelta.addModification(oldRepoAttrPropDelta);
} else {
MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(attrDef.getMatchingRuleQName(), attrDef.getTypeName());
List<PrismPropertyValue> valuesToAdd = null;
List<PrismPropertyValue> valuesToDelete = null;
for (PrismPropertyValue attrVal: attrProperty.getValues()) {
Object currentRealValue = attrVal.getValue();
Object normalizedRealValue = matchingRule.normalize(currentRealValue);
if (!normalizedRealValue.equals(currentRealValue)) {
if (attrDef.isSingleValue()) {
shadowDelta.addModificationReplaceProperty(attrProperty.getPath(), normalizedRealValue);
break;
} else {
if (valuesToAdd == null) {
valuesToAdd = new ArrayList<>();
}
valuesToAdd.add(new PrismPropertyValue(normalizedRealValue));
if (valuesToDelete == null) {
valuesToDelete = new ArrayList<>();
}
valuesToDelete.add(new PrismPropertyValue(currentRealValue));
}
}
}
PropertyDelta attrDelta = attrProperty.createDelta(attrProperty.getPath());
if (valuesToAdd != null) {
attrDelta.addValuesToAdd(valuesToAdd);
}
if (valuesToDelete != null) {
attrDelta.addValuesToDelete(valuesToDelete);
}
shadowDelta.addModification(attrDelta);
}
}
}
if (!shadowDelta.isEmpty()) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Fixing shadow {} with delta:\n{}", origRepoShadow, shadowDelta.debugDump());
}
try {
repositoryService.modifyObject(ShadowType.class, origRepoShadow.getOid(), shadowDelta.getModifications(), parentResult);
} catch (ObjectAlreadyExistsException e) {
// This should not happen for shadows
throw new SystemException(e.getMessage(), e);
}
shadowDelta.applyTo(currentRepoShadow);
} else {
LOGGER.trace("No need to fixing shadow {} (empty delta)", origRepoShadow);
}
} else {
LOGGER.trace("No need to fixing shadow {} (no atttributes)", origRepoShadow);
}
return currentRepoShadow;
}
public void setKindIfNecessary(ShadowType repoShadowType, RefinedObjectClassDefinition objectClassDefinition) {
if (repoShadowType.getKind() == null && objectClassDefinition != null) {
repoShadowType.setKind(objectClassDefinition.getKind());
}
}
public void setIntentIfNecessary(ShadowType repoShadowType, RefinedObjectClassDefinition objectClassDefinition) {
if (repoShadowType.getIntent() == null && objectClassDefinition.getIntent() != null) {
repoShadowType.setIntent(objectClassDefinition.getIntent());
}
}
public void normalizeAttributes(PrismObject<ShadowType> shadow, RefinedObjectClassDefinition objectClassDefinition) throws SchemaException {
for (ResourceAttribute<?> attribute: ShadowUtil.getAttributes(shadow)) {
RefinedAttributeDefinition rAttrDef = objectClassDefinition.findAttributeDefinition(attribute.getElementName());
normalizeAttribute(attribute, rAttrDef);
}
}
private <T> void normalizeAttribute(ResourceAttribute<T> attribute, RefinedAttributeDefinition rAttrDef) throws SchemaException {
MatchingRule<T> matchingRule = matchingRuleRegistry.getMatchingRule(rAttrDef.getMatchingRuleQName(), rAttrDef.getTypeName());
if (matchingRule != null) {
for (PrismPropertyValue<T> pval: attribute.getValues()) {
T normalizedRealValue = matchingRule.normalize(pval.getValue());
pval.setValue(normalizedRealValue);
}
}
}
public <T> void normalizeDeltas(Collection<? extends ItemDelta<PrismPropertyValue<T>,PrismPropertyDefinition<T>>> deltas,
RefinedObjectClassDefinition objectClassDefinition) throws SchemaException {
for (ItemDelta<PrismPropertyValue<T>,PrismPropertyDefinition<T>> delta : deltas) {
normalizeDelta(delta, objectClassDefinition);
}
}
public <T> void normalizeDelta(ItemDelta<PrismPropertyValue<T>,PrismPropertyDefinition<T>> delta,
RefinedObjectClassDefinition objectClassDefinition) throws SchemaException {
if (!ShadowType.F_ATTRIBUTES.equals(ItemPath.getName(delta.getPath().first()))){
return;
}
RefinedAttributeDefinition rAttrDef = objectClassDefinition.findAttributeDefinition(delta.getElementName());
if (rAttrDef == null){
throw new SchemaException("Failed to normalize attribute: " + delta.getElementName()+ ". Definition for this attribute doesn't exist.");
}
normalizeDelta(delta, rAttrDef);
}
private <T> void normalizeDelta(ItemDelta<PrismPropertyValue<T>,PrismPropertyDefinition<T>> delta, RefinedAttributeDefinition rAttrDef) throws SchemaException{
MatchingRule<T> matchingRule = matchingRuleRegistry.getMatchingRule(rAttrDef.getMatchingRuleQName(), rAttrDef.getTypeName());
if (matchingRule != null) {
if (delta.getValuesToReplace() != null){
normalizeValues(delta.getValuesToReplace(), matchingRule);
}
if (delta.getValuesToAdd() != null){
normalizeValues(delta.getValuesToAdd(), matchingRule);
}
if (delta.getValuesToDelete() != null){
normalizeValues(delta.getValuesToDelete(), matchingRule);
}
}
}
private <T> void normalizeValues(Collection<PrismPropertyValue<T>> values, MatchingRule<T> matchingRule) throws SchemaException {
for (PrismPropertyValue<T> pval: values) {
T normalizedRealValue = matchingRule.normalize(pval.getValue());
pval.setValue(normalizedRealValue);
}
}
<T> T getNormalizedAttributeValue(PrismPropertyValue<T> pval, RefinedAttributeDefinition rAttrDef) throws SchemaException {
MatchingRule<T> matchingRule = matchingRuleRegistry.getMatchingRule(rAttrDef.getMatchingRuleQName(), rAttrDef.getTypeName());
if (matchingRule != null) {
T normalizedRealValue = matchingRule.normalize(pval.getValue());
return normalizedRealValue;
} else {
return pval.getValue();
}
}
private <T> Collection<T> getNormalizedAttributeValues(ResourceAttribute<T> attribute, RefinedAttributeDefinition rAttrDef) throws SchemaException {
MatchingRule<T> matchingRule = matchingRuleRegistry.getMatchingRule(rAttrDef.getMatchingRuleQName(), rAttrDef.getTypeName());
if (matchingRule == null) {
return attribute.getRealValues();
} else {
Collection<T> normalizedValues = new ArrayList<T>();
for (PrismPropertyValue<T> pval: attribute.getValues()) {
T normalizedRealValue = matchingRule.normalize(pval.getValue());
normalizedValues.add(normalizedRealValue);
}
return normalizedValues;
}
}
public <T> boolean compareAttribute(RefinedObjectClassDefinition refinedObjectClassDefinition,
ResourceAttribute<T> attributeA, T... valuesB) throws SchemaException {
RefinedAttributeDefinition refinedAttributeDefinition = refinedObjectClassDefinition.findAttributeDefinition(attributeA.getElementName());
Collection<T> valuesA = getNormalizedAttributeValues(attributeA, refinedAttributeDefinition);
return MiscUtil.unorderedCollectionEquals(valuesA, Arrays.asList(valuesB));
}
public <T> boolean compareAttribute(RefinedObjectClassDefinition refinedObjectClassDefinition,
ResourceAttribute<T> attributeA, ResourceAttribute<T> attributeB) throws SchemaException {
RefinedAttributeDefinition refinedAttributeDefinition = refinedObjectClassDefinition.findAttributeDefinition(attributeA.getElementName());
Collection<T> valuesA = getNormalizedAttributeValues(attributeA, refinedAttributeDefinition);
refinedAttributeDefinition = refinedObjectClassDefinition.findAttributeDefinition(attributeA.getElementName());
Collection<T> valuesB = getNormalizedAttributeValues(attributeB, refinedAttributeDefinition);
return MiscUtil.unorderedCollectionEquals(valuesA, valuesB);
}
}