/*
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.model.impl.validator;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl;
import com.evolveum.midpoint.model.api.validator.Issue;
import com.evolveum.midpoint.model.api.validator.ResourceValidator;
import com.evolveum.midpoint.model.api.validator.Scope;
import com.evolveum.midpoint.model.api.validator.ValidationResult;
import com.evolveum.midpoint.model.impl.util.DataModelUtil;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceSchema;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.xml.namespace.QName;
import java.text.MessageFormat;
import java.util.*;
import static com.evolveum.midpoint.schema.util.ResourceTypeUtil.fillDefault;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.SynchronizationSituationType.*;
/**
* EXPERIMENTAL
*
* TODO:
* - existence of dependent kind/intent/resource (in thorough scope)
* - checking references (thorough)
* - mapping: unknown channel / except-channel
* - empty mapping (?)
* - iteration tokens
* - invalid objectclass in synchronization
* - invalid focus type in synchronization
* - empty correlation, correlation condition?
* - empty confirmation condition?
* - empty synchronization condition?
*
* @author mederly
*/
@Component(value = "resourceValidator")
public class ResourceValidatorImpl implements ResourceValidator {
public static final String CLASS_DOT = ResourceValidator.class.getSimpleName() + ".";
private static final ItemPath ITEM_PATH_SYNCHRONIZATION = new ItemPath(ResourceType.F_SYNCHRONIZATION, SynchronizationType.F_OBJECT_SYNCHRONIZATION);
private static final ItemPath ITEM_PATH_SCHEMA_HANDLING = new ItemPath(ResourceType.F_SCHEMA_HANDLING, SchemaHandlingType.F_OBJECT_TYPE);
@Autowired
private MatchingRuleRegistry matchingRuleRegistry;
@Autowired
private PrismContext prismContext;
private class ResourceValidationContext {
@NotNull final PrismObject<ResourceType> resourceObject;
@NotNull final ObjectReferenceType resourceRef;
@NotNull final Scope scope;
@NotNull final Task task;
@NotNull final ValidationResult validationResult;
@NotNull final ResourceBundle bundle;
final ResourceSchema resourceSchema;
public ResourceValidationContext(
@NotNull PrismObject<ResourceType> resourceObject,
@NotNull Scope scope, @NotNull Task task,
@NotNull ValidationResult validationResult, ResourceSchema resourceSchema, ResourceBundle bundle) {
this.resourceObject = resourceObject;
this.resourceRef = ObjectTypeUtil.createObjectRef(resourceObject);
this.scope = scope;
this.task = task;
this.validationResult = validationResult;
this.resourceSchema = resourceSchema;
this.bundle = bundle;
}
}
@NotNull
@Override
public ValidationResult validate(@NotNull PrismObject<ResourceType> resourceObject, @NotNull Scope scope,
@Nullable Locale locale, @NotNull Task task, @NotNull OperationResult result) {
final ResourceType resource = resourceObject.asObjectable();
final ValidationResult vr = new ValidationResult();
ResourceBundle bundle = ResourceBundle.getBundle(
SchemaConstants.SCHEMA_LOCALIZATION_PROPERTIES_RESOURCE_BASE_PATH,
locale != null ? locale : Locale.getDefault());
ResourceSchema resourceSchema = null;
try {
resourceSchema = RefinedResourceSchemaImpl.getResourceSchema(resourceObject, prismContext);
} catch (Throwable t) {
vr.add(Issue.Severity.WARNING, CAT_SCHEMA, C_NO_SCHEMA,
getString(bundle, CLASS_DOT + C_NO_SCHEMA, t.getMessage()),
ObjectTypeUtil.createObjectRef(resourceObject), ItemPath.EMPTY_PATH);
}
ResourceValidationContext ctx = new ResourceValidationContext(resourceObject, scope, task, vr, resourceSchema, bundle);
SchemaHandlingType schemaHandling = resource.getSchemaHandling();
if (schemaHandling != null) {
checkSchemaHandlingDuplicateObjectTypes(ctx, schemaHandling);
checkSchemaHandlingDefaults(ctx, schemaHandling);
checkSchemaHandlingObjectTypes(ctx, schemaHandling);
}
SynchronizationType synchronization = resource.getSynchronization();
if (synchronization != null) {
checkSynchronizationDuplicateObjectTypes(ctx, synchronization);
int i = 1;
for (ObjectSynchronizationType objectSync : resource.getSynchronization().getObjectSynchronization()) {
checkObjectSynchronization(ctx, new ItemPath(ResourceType.F_SYNCHRONIZATION, SynchronizationType.F_OBJECT_SYNCHRONIZATION, i), objectSync);
i++;
}
}
checkSynchronizationExistenceForSchemaHandlingObjectTypes(ctx);
checkSchemaHandlingExistenceForSynchronizationObjectTypes(ctx);
return ctx.validationResult;
}
private void checkSchemaHandlingObjectTypes(ResourceValidationContext ctx, SchemaHandlingType schemaHandling) {
int i = 1;
for (ResourceObjectTypeDefinitionType objectType : schemaHandling.getObjectType()) {
ItemPath path = new ItemPath(ResourceType.F_SCHEMA_HANDLING, SchemaHandlingType.F_OBJECT_TYPE, i);
checkSchemaHandlingObjectType(ctx, path, objectType);
i++;
}
}
private void checkSchemaHandlingObjectType(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType) {
checkDuplicateItems(ctx, path, objectType);
checkObjectClass(ctx, path, objectType);
ObjectClassComplexTypeDefinition ocdef = null;
if (ctx.resourceSchema != null && objectType.getObjectClass() != null) {
ocdef = ctx.resourceSchema.findObjectClassDefinition(objectType.getObjectClass());
checkObjectClassDefinition(ctx, path, objectType, ocdef);
}
int i = 1;
for (ResourceAttributeDefinitionType attributeDef : objectType.getAttribute()) {
checkSchemaHandlingAttribute(ctx, ocdef, path.append(new ItemPath(ResourceObjectTypeDefinitionType.F_ATTRIBUTE, i)), objectType, attributeDef);
i++;
}
i = 1;
for (ResourceObjectAssociationType associationDef : objectType.getAssociation()) {
checkSchemaHandlingAssociation(ctx, ocdef, path.append(new ItemPath(ResourceObjectTypeDefinitionType.F_ATTRIBUTE, i)), objectType, associationDef);
i++;
}
if (objectType.getActivation() != null) {
ItemPath actPath = path.append(ResourceObjectTypeDefinitionType.F_ACTIVATION);
checkBidirectionalMapping(ctx, actPath, ResourceActivationDefinitionType.F_ADMINISTRATIVE_STATUS, objectType, objectType.getActivation().getAdministrativeStatus());
checkBidirectionalMapping(ctx, actPath, ResourceActivationDefinitionType.F_EXISTENCE, objectType, objectType.getActivation().getExistence());
checkBidirectionalMapping(ctx, actPath, ResourceActivationDefinitionType.F_LOCKOUT_STATUS, objectType, objectType.getActivation().getLockoutStatus());
checkBidirectionalMapping(ctx, actPath, ResourceActivationDefinitionType.F_VALID_FROM, objectType, objectType.getActivation().getValidFrom());
checkBidirectionalMapping(ctx, actPath, ResourceActivationDefinitionType.F_VALID_TO, objectType, objectType.getActivation().getValidTo());
}
if (objectType.getCredentials() != null) {
ItemPath credPath = path.append(ResourceObjectTypeDefinitionType.F_CREDENTIALS);
checkPasswordMapping(ctx, credPath, ResourceCredentialsDefinitionType.F_PASSWORD, objectType, objectType.getCredentials().getPassword());
}
checkDependencies(ctx, path, objectType);
}
private void checkBidirectionalMapping(ResourceValidationContext ctx, ItemPath path, QName itemName, ResourceObjectTypeDefinitionType objectType,
@Nullable ResourceBidirectionalMappingType bidirectionalMapping) {
if (bidirectionalMapping == null) {
return;
}
ItemPath itemPath = path.append(itemName);
int i = 1;
for (MappingType inbound : bidirectionalMapping.getInbound()) {
checkMapping(ctx, itemPath.append(ResourceBidirectionalMappingType.F_INBOUND), objectType, itemName, inbound, false, i, true);
i++;
}
i = 1;
for (MappingType outbound : bidirectionalMapping.getOutbound()) {
checkMapping(ctx, itemPath.append(ResourceBidirectionalMappingType.F_OUTBOUND), objectType, itemName, outbound, true, i, true);
i++;
}
}
private void checkPasswordMapping(ResourceValidationContext ctx, ItemPath path, QName itemName, ResourceObjectTypeDefinitionType objectType,
@Nullable ResourcePasswordDefinitionType passwordDefinition) {
if (passwordDefinition == null) {
return;
}
ItemPath itemPath = path.append(itemName);
int i = 1;
for (MappingType inbound : passwordDefinition.getInbound()) {
checkMapping(ctx, itemPath.append(ResourcePasswordDefinitionType.F_INBOUND), objectType, itemName, inbound, false, i, true);
i++;
}
i = 1;
for (MappingType outbound : passwordDefinition.getOutbound()) {
checkMapping(ctx, itemPath.append(ResourcePasswordDefinitionType.F_OUTBOUND), objectType, itemName, outbound, true, i, true);
i++;
}
}
private void checkDependencies(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType) {
for (ResourceObjectTypeDependencyType dependency : objectType.getDependency()) {
if (dependency.getResourceRef() == null ||
(ctx.resourceObject.getOid() != null && ctx.resourceObject.getOid().equals(dependency.getResourceRef().getOid()))) {
if (ResourceTypeUtil.findObjectTypeDefinition(ctx.resourceObject, dependency.getKind(), dependency.getIntent()) == null) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, C_DEPENDENT_OBJECT_TYPE_DOES_NOT_EXIST,
getString(ctx.bundle, CLASS_DOT + C_DEPENDENT_OBJECT_TYPE_DOES_NOT_EXIST, getName(objectType),
fillDefault(dependency.getKind()) + "/" + fillDefault(dependency.getIntent())),
ctx.resourceRef, path.append(ResourceObjectTypeDefinitionType.F_DEPENDENCY));
}
} else {
// check in 'remote' resource only if thorough validation is required
}
}
}
private void checkObjectClassDefinition(ResourceValidationContext ctx, ItemPath path,
ResourceObjectTypeDefinitionType objectType, ObjectClassComplexTypeDefinition ocdef) {
if (ocdef == null) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, C_UNKNOWN_OBJECT_CLASS,
getString(ctx.bundle, CLASS_DOT + C_UNKNOWN_OBJECT_CLASS, getName(objectType), objectType.getObjectClass()),
ctx.resourceRef, path.append(ResourceObjectTypeDefinitionType.F_OBJECT_CLASS));
}
}
private void checkObjectClass(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType) {
if (objectType.getObjectClass() == null) {
ctx.validationResult.add(Issue.Severity.ERROR,
CAT_SCHEMA_HANDLING, C_MISSING_OBJECT_CLASS,
getString(ctx.bundle, CLASS_DOT + C_MISSING_OBJECT_CLASS, getName(objectType)),
ctx.resourceRef, path.append(ResourceObjectTypeDefinitionType.F_OBJECT_CLASS));
}
}
private void checkDuplicateItems(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType) {
Set<QName> existingNames = new HashSet<>();
Set<QName> duplicates = new HashSet<>();
for (ItemRefinedDefinitionType def : objectType.getAttribute()) {
registerName(ctx, existingNames, duplicates, def.getRef());
}
for (ItemRefinedDefinitionType def : objectType.getAssociation()) {
registerName(ctx, existingNames, duplicates, def.getRef());
}
if (!duplicates.isEmpty()) {
ctx.validationResult.add(Issue.Severity.ERROR,
CAT_SCHEMA_HANDLING, C_MULTIPLE_ITEMS,
getString(ctx.bundle, CLASS_DOT + C_MULTIPLE_ITEMS, getName(objectType), prettyPrintUsingStandardPrefix(duplicates)),
ctx.resourceRef, path);
}
}
private void registerName(ResourceValidationContext ctx, Set<QName> existingNames, Set<QName> duplicates, ItemPathType ref) {
QName name = itemRefToName(ref);
if (name != null) {
if (name.getNamespaceURI() == null) {
name = new QName(ResourceTypeUtil.getResourceNamespace(ctx.resourceObject), name.getLocalPart()); // TODO is this correct?
}
if (!existingNames.add(name)) {
duplicates.add(name);
}
}
}
private void checkSchemaHandlingAttribute(ResourceValidationContext ctx, ObjectClassComplexTypeDefinition ocdef,
ItemPath path,
ResourceObjectTypeDefinitionType objectType, ResourceAttributeDefinitionType attributeDef) {
QName ref = itemRefToName(attributeDef.getRef());
checkSchemaHandlingItem(ctx, path, objectType, attributeDef);
ResourceAttributeDefinition<?> rad = null;
// TODO rewrite using CompositeRefinedObjectClassDefinition
if (ref != null) {
boolean caseIgnoreAttributeNames = ResourceTypeUtil.isCaseIgnoreAttributeNames(ctx.resourceObject.asObjectable());
if (ocdef != null) {
rad = ocdef.findAttributeDefinition(ref, caseIgnoreAttributeNames);
}
if (rad == null) {
for (QName auxOcName : objectType.getAuxiliaryObjectClass()) {
ObjectClassComplexTypeDefinition auxOcDef = ctx.resourceSchema.findObjectClassDefinition(auxOcName);
if (auxOcDef != null) {
rad = auxOcDef.findAttributeDefinition(ref, caseIgnoreAttributeNames);
if (rad != null) {
break;
}
}
}
}
if (rad == null) {
ctx.validationResult.add(Issue.Severity.ERROR,
CAT_SCHEMA_HANDLING, C_UNKNOWN_ATTRIBUTE_NAME,
getString(ctx.bundle, CLASS_DOT + C_UNKNOWN_ATTRIBUTE_NAME, getName(objectType), ref, objectType.getObjectClass()),
ctx.resourceRef, path.append(ResourceItemDefinitionType.F_REF));
}
}
checkItemRef(ctx, path, objectType, attributeDef, C_NO_ATTRIBUTE_REF);
checkMatchingRule(ctx, path, objectType, attributeDef, ref, rad);
}
private void checkMapping(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType,
QName itemName, MappingType mapping, boolean outbound, int index, boolean implicitSourceOrTarget) {
String inOut = outbound ? getString(ctx.bundle, "ResourceValidator.outboundMapping") : getString(ctx.bundle, "ResourceValidator.inboundMapping", index);
String itemNameText = prettyPrintUsingStandardPrefix(itemName);
if (outbound && mapping.getTarget() != null) {
ctx.validationResult.add(Issue.Severity.INFO,
CAT_SCHEMA_HANDLING, C_SUPERFLUOUS_MAPPING_TARGET,
getString(ctx.bundle, CLASS_DOT + C_SUPERFLUOUS_MAPPING_TARGET, getName(objectType),
inOut, itemNameText, format(mapping.getTarget())),
ctx.resourceRef, path);
}
if (!implicitSourceOrTarget && (mapping.getExpression() == null || mapping.getExpression().getExpressionEvaluator().isEmpty() ||
(mapping.getExpression().getExpressionEvaluator().size() == 1 &&
QNameUtil.match(mapping.getExpression().getExpressionEvaluator().get(0).getName(), SchemaConstantsGenerated.C_AS_IS)))) {
if ((outbound && (mapping.getSource() == null || mapping.getSource().isEmpty())) || (!outbound && mapping.getTarget() == null)) {
String code = outbound ? C_MISSING_MAPPING_SOURCE : C_MISSING_MAPPING_TARGET;
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, code,
getString(ctx.bundle, CLASS_DOT + code, getName(objectType), inOut, itemNameText),
ctx.resourceRef, path);
}
}
for (VariableBindingDefinitionType source : mapping.getSource()) {
checkItemPath(ctx, path, objectType, itemName, mapping, outbound, itemNameText, true, index, source.getPath());
}
if (mapping.getTarget() != null) {
checkItemPath(ctx, path, objectType, itemName, mapping, outbound, itemNameText, false, index, mapping.getTarget().getPath());
}
}
private void checkItemPath(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType,
QName itemDef, MappingType mapping, boolean outbound, String itemNameText, boolean isSource, int index, @Nullable ItemPathType mappingPath) {
if (mappingPath == null) {
return;
}
DataModelUtil.PathResolutionContext pctx = new DataModelUtil.ResourceResolutionContext(prismContext, ExpressionConstants.VAR_FOCUS,
ctx.resourceObject.asObjectable(), fillDefault(objectType.getKind()), fillDefault(objectType.getIntent()));
DataModelUtil.PathResolutionResult result = DataModelUtil.resolvePath(mappingPath.getItemPath(), pctx);
if (result == null) {
// i.e. not implemented -> no reports
} else if (result.getDefinition() != null) {
// definition found => OK (ignoring any potential issues found)
} else {
String inOut = outbound ? getString(ctx.bundle, "ResourceValidator.outboundMapping") : getString(ctx.bundle, "ResourceValidator.inboundMapping", index);
Issue.Severity severity = Issue.getSeverity(result.getIssues());
if (severity == null) {
severity = Issue.Severity.INFO;
}
String code;
if (severity != Issue.Severity.INFO) {
code = isSource ? C_INVALID_MAPPING_SOURCE : C_INVALID_MAPPING_TARGET;
} else {
code = isSource ? C_SUSPICIOUS_MAPPING_SOURCE : C_SUSPICIOUS_MAPPING_TARGET;
}
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Issue issue : result.getIssues()) {
if (first) first = false; else sb.append("; ");
sb.append(issue.getText());
}
ctx.validationResult.add(severity, CAT_SCHEMA_HANDLING, code,
getString(ctx.bundle, CLASS_DOT + code, getName(objectType), inOut, itemNameText, sb.toString()),
ctx.resourceRef, path);
}
}
private String format(VariableBindingDefinitionType target) {
return target != null ? format(target.getPath()) : "";
}
private String format(ItemPathType path) {
return path != null ? path.toString() : "";
}
private String format(List<?> items) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object o : items) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(o);
}
return sb.toString();
}
@Nullable
private QName itemRefToName(ItemPathType ref) {
return ItemPath.asSingleName(ref != null ? ref.getItemPath() : null);
}
private void checkMatchingRule(ResourceValidationContext ctx, ItemPath path,
ResourceObjectTypeDefinitionType objectType, ResourceAttributeDefinitionType attributeDef, QName ref, ResourceAttributeDefinition<?> rad) {
QName matchingRule = attributeDef.getMatchingRule();
if (matchingRule == null) {
return;
}
try {
matchingRuleRegistry.getMatchingRule(matchingRule, rad != null ? rad.getTypeName() : null);
} catch (Throwable t) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, C_WRONG_MATCHING_RULE,
getString(ctx.bundle, CLASS_DOT + C_WRONG_MATCHING_RULE, getName(objectType), prettyPrintUsingStandardPrefix(ref), t.getMessage()),
ctx.resourceRef, path.append(ResourceItemDefinitionType.F_MATCHING_RULE));
}
}
private void checkSchemaHandlingAssociation(ResourceValidationContext ctx, ObjectClassComplexTypeDefinition ocdef, ItemPath path,
ResourceObjectTypeDefinitionType objectType, ResourceObjectAssociationType associationDef) {
checkSchemaHandlingItem(ctx, path, objectType, associationDef);
checkItemRef(ctx, path, objectType, associationDef, C_NO_ASSOCIATION_NAME);
checkNotEmpty(ctx, path, objectType, associationDef, associationDef.getKind(), ResourceObjectAssociationType.F_KIND, C_MISSING_ASSOCIATION_TARGET_KIND);
checkNotEmpty(ctx, path, objectType, associationDef, associationDef.getIntent(), ResourceObjectAssociationType.F_INTENT, C_MISSING_ASSOCIATION_TARGET_INTENT);
checkNotEmpty(ctx, path, objectType, associationDef, associationDef.getDirection(), ResourceObjectAssociationType.F_DIRECTION, C_MISSING_ASSOCIATION_DIRECTION);
checkNotEmpty(ctx, path, objectType, associationDef, associationDef.getAssociationAttribute(), ResourceObjectAssociationType.F_ASSOCIATION_ATTRIBUTE, C_MISSING_ASSOCIATION_ASSOCIATION_ATTRIBUTE);
checkNotEmpty(ctx, path, objectType, associationDef, associationDef.getValueAttribute(), ResourceObjectAssociationType.F_VALUE_ATTRIBUTE, C_MISSING_ASSOCIATION_VALUE_ATTRIBUTE);
// TODO checking matching rule for associations?
QName ref = itemRefToName(associationDef.getRef());
if (ocdef != null) {
if (ref != null) {
if (ocdef.findAttributeDefinition(ref, ResourceTypeUtil.isCaseIgnoreAttributeNames(ctx.resourceObject.asObjectable())) != null) {
ctx.validationResult.add(Issue.Severity.ERROR,
CAT_SCHEMA_HANDLING, C_COLLIDING_ASSOCIATION_NAME,
getString(ctx.bundle, CLASS_DOT + C_COLLIDING_ASSOCIATION_NAME, getName(objectType), prettyPrintUsingStandardPrefix(ref)),
ctx.resourceRef, path.append(ResourceItemDefinitionType.F_REF));
}
}
}
for (String intent : associationDef.getIntent()) {
checkAssociationTargetIntent(ctx, path, objectType, associationDef, ref, intent);
}
}
private void checkAssociationTargetIntent(ResourceValidationContext ctx, ItemPath path,
ResourceObjectTypeDefinitionType objectType, ResourceObjectAssociationType associationDef, QName ref, String intent) {
if (ResourceTypeUtil.findObjectTypeDefinition(ctx.resourceObject, associationDef.getKind(), intent) == null) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, C_TARGET_OBJECT_TYPE_DOES_NOT_EXIST,
getString(ctx.bundle, CLASS_DOT + C_TARGET_OBJECT_TYPE_DOES_NOT_EXIST, getName(objectType),
fillDefault(associationDef.getKind()) + "/" + fillDefault(intent),
prettyPrintUsingStandardPrefix(ref)),
ctx.resourceRef, path);
}
}
private void checkNotEmpty(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType,
ResourceObjectAssociationType associationDef, Object object, QName name, String errorCode) {
if (object == null) {
ctx.validationResult.add(Issue.Severity.ERROR, CAT_SCHEMA_HANDLING, errorCode,
getString(ctx.bundle, CLASS_DOT + errorCode, getName(objectType), String.valueOf(associationDef.getRef())), ctx.resourceRef, new ItemPath(path, name));
}
}
private void checkNotEmpty(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType,
ResourceObjectAssociationType associationDef, Collection<?> values, QName name, String errorCode) {
if (values == null || values.isEmpty()) {
ctx.validationResult.add(Issue.Severity.ERROR, CAT_SCHEMA_HANDLING, errorCode,
getString(ctx.bundle, CLASS_DOT + errorCode, getName(objectType), String.valueOf(associationDef.getRef())), ctx.resourceRef, new ItemPath(path, name));
}
}
private void checkItemRef(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType,
ResourceItemDefinitionType itemDef, String noRefKey) {
ItemPath refPath = itemDef.getRef() != null ? itemDef.getRef().getItemPath() : null;
if (ItemPath.isNullOrEmpty(refPath)) {
ctx.validationResult.add(Issue.Severity.ERROR, CAT_SCHEMA_HANDLING, noRefKey,
getString(ctx.bundle, CLASS_DOT + noRefKey, getName(objectType)),
ctx.resourceRef, path.append(ItemRefinedDefinitionType.F_REF));
} else if (refPath.size() > 1 || !(refPath.getSegments().get(0) instanceof NameItemPathSegment)) {
ctx.validationResult.add(Issue.Severity.ERROR, CAT_SCHEMA_HANDLING, C_WRONG_ITEM_NAME,
getString(ctx.bundle, CLASS_DOT + C_WRONG_ITEM_NAME, getName(objectType), refPath.toString()),
ctx.resourceRef, path.append(ItemRefinedDefinitionType.F_REF));
} else if (StringUtils.isBlank(refPath.asSingleName().getNamespaceURI())) {
ctx.validationResult.add(Issue.Severity.WARNING, CAT_SCHEMA_HANDLING, C_NO_ITEM_NAMESPACE,
getString(ctx.bundle, CLASS_DOT + C_NO_ITEM_NAMESPACE, getName(objectType), refPath.toString()),
ctx.resourceRef, path.append(ItemRefinedDefinitionType.F_REF));
}
}
private void checkSchemaHandlingItem(ResourceValidationContext ctx, ItemPath path, ResourceObjectTypeDefinitionType objectType,
ResourceItemDefinitionType itemDef) {
QName itemName = itemRefToName(itemDef.getRef());
if (itemDef.getOutbound() != null) {
checkMapping(ctx, path.append(ResourceItemDefinitionType.F_OUTBOUND), objectType, itemName, itemDef.getOutbound(), true, 0, false);
}
int i = 1;
for (MappingType inbound : itemDef.getInbound()) {
checkMapping(ctx, path.append(new ItemPath(ResourceItemDefinitionType.F_INBOUND, i)), objectType, itemName, inbound, false, i, false);
i++;
}
}
private void checkSchemaHandlingDuplicateObjectTypes(ResourceValidationContext ctx, SchemaHandlingType schemaHandling) {
DuplicateObjectTypeDetector detector = new DuplicateObjectTypeDetector(schemaHandling);
if (detector.hasDuplicates()) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, C_MULTIPLE_SCHEMA_HANDLING_DEFINITIONS,
getString(ctx.bundle, CLASS_DOT + C_MULTIPLE_SCHEMA_HANDLING_DEFINITIONS, detector.getDuplicatesList()),
ctx.resourceRef, new ItemPath(ResourceType.F_SCHEMA_HANDLING, SchemaHandlingType.F_OBJECT_TYPE));
}
}
private void checkSchemaHandlingDefaults(ResourceValidationContext ctx, SchemaHandlingType schemaHandling) {
int defAccount = 0, defEntitlement = 0, defGeneric = 0;
int totalAccount = 0;
for (ResourceObjectTypeDefinitionType def : schemaHandling.getObjectType()) {
if (Boolean.TRUE.equals(def.isDefault())) {
switch (fillDefault(def.getKind())) {
case ACCOUNT:
defAccount++;
break;
case ENTITLEMENT:
defEntitlement++;
break;
case GENERIC:
defGeneric++;
break;
default:
throw new IllegalStateException();
}
}
if (fillDefault(def.getKind()) == ShadowKindType.ACCOUNT) {
totalAccount++;
}
}
checkMultipleDefaultDefinitions(ctx, ShadowKindType.ACCOUNT, defAccount);
checkMultipleDefaultDefinitions(ctx, ShadowKindType.ENTITLEMENT, defEntitlement);
checkMultipleDefaultDefinitions(ctx, ShadowKindType.GENERIC, defGeneric);
if (totalAccount > 0 && defAccount == 0) {
ctx.validationResult.add(Issue.Severity.INFO,
CAT_SCHEMA_HANDLING, C_NO_DEFAULT_ACCOUNT_SCHEMA_HANDLING_DEFAULT_DEFINITION,
getString(ctx.bundle, CLASS_DOT + C_NO_DEFAULT_ACCOUNT_SCHEMA_HANDLING_DEFAULT_DEFINITION),
ctx.resourceRef, new ItemPath(ResourceType.F_SCHEMA_HANDLING, SchemaHandlingType.F_OBJECT_TYPE));
}
}
private void checkMultipleDefaultDefinitions(ResourceValidationContext ctx, ShadowKindType kind, int count) {
if (count > 1) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SCHEMA_HANDLING, C_MULTIPLE_SCHEMA_HANDLING_DEFAULT_DEFINITIONS,
getString(ctx.bundle, CLASS_DOT + C_MULTIPLE_SCHEMA_HANDLING_DEFAULT_DEFINITIONS, kind),
ctx.resourceRef, new ItemPath(ResourceType.F_SCHEMA_HANDLING, SchemaHandlingType.F_OBJECT_TYPE));
}
}
private void checkSynchronizationDuplicateObjectTypes(ResourceValidationContext ctx, SynchronizationType synchronization) {
DuplicateObjectTypeDetector detector = new DuplicateObjectTypeDetector(synchronization);
if (detector.hasDuplicates()) {
ctx.validationResult.add(Issue.Severity.WARNING,
CAT_SYNCHRONIZATION, C_MULTIPLE_SYNCHRONIZATION_DEFINITIONS,
getString(ctx.bundle, CLASS_DOT + C_MULTIPLE_SYNCHRONIZATION_DEFINITIONS, detector.getDuplicatesList()),
ctx.resourceRef, ITEM_PATH_SYNCHRONIZATION);
}
}
private void checkSynchronizationExistenceForSchemaHandlingObjectTypes(ResourceValidationContext ctx) {
ResourceType resource = ctx.resourceObject.asObjectable();
Set<ObjectTypeRecord> schemaHandlingFor = new HashSet<>(ObjectTypeRecord.extractFrom(resource.getSchemaHandling()));
Collection<ObjectTypeRecord> synchronizationFor = ObjectTypeRecord.extractFrom(resource.getSynchronization());
schemaHandlingFor.removeAll(synchronizationFor);
if (!schemaHandlingFor.isEmpty()) {
ctx.validationResult.add(Issue.Severity.INFO, CAT_SYNCHRONIZATION, C_NO_SYNCHRONIZATION_DEFINITION,
getString(ctx.bundle, CLASS_DOT + C_NO_SYNCHRONIZATION_DEFINITION, ObjectTypeRecord.asFormattedList(schemaHandlingFor)),
ctx.resourceRef, ITEM_PATH_SYNCHRONIZATION);
}
}
private void checkSchemaHandlingExistenceForSynchronizationObjectTypes(ResourceValidationContext ctx) {
ResourceType resource = ctx.resourceObject.asObjectable();
Set<ObjectTypeRecord> synchronizationFor = new HashSet<>(ObjectTypeRecord.extractFrom(resource.getSynchronization()));
Collection<ObjectTypeRecord> schemaHandlingFor = ObjectTypeRecord.extractFrom(resource.getSchemaHandling());
synchronizationFor.removeAll(schemaHandlingFor);
if (!synchronizationFor.isEmpty()) {
ctx.validationResult.add(Issue.Severity.INFO, CAT_SCHEMA_HANDLING, C_NO_SCHEMA_HANDLING_DEFINITION,
getString(ctx.bundle, CLASS_DOT + C_NO_SCHEMA_HANDLING_DEFINITION, ObjectTypeRecord.asFormattedList(synchronizationFor)),
ctx.resourceRef, ITEM_PATH_SCHEMA_HANDLING);
}
}
private void checkObjectSynchronization(ResourceValidationContext ctx, ItemPath path, ObjectSynchronizationType objectSync) {
Map<SynchronizationSituationType,Integer> counts = new HashMap<>();
for (SynchronizationReactionType reaction : objectSync.getReaction()) {
if (reaction.getSituation() == null) {
ctx.validationResult.add(Issue.Severity.WARNING, CAT_SYNCHRONIZATION, C_NO_SITUATION,
getString(ctx.bundle, CLASS_DOT + C_NO_SITUATION, getName(objectSync)),
ctx.resourceRef, path);
} else {
Integer c = counts.get(reaction.getSituation());
counts.put(reaction.getSituation(), c != null ? c+1 : 1);
}
}
checkMissingReactions(ctx, path, objectSync, counts, Collections.singletonList(UNLINKED));
checkDuplicateReactions(ctx, path, objectSync, counts);
if (objectSync.getCorrelation().isEmpty()) {
ctx.validationResult.add(Issue.Severity.WARNING, CAT_SYNCHRONIZATION, C_NO_CORRELATION_RULE,
getString(ctx.bundle, CLASS_DOT + C_NO_CORRELATION_RULE, getName(objectSync)),
ctx.resourceRef, path);
}
}
private void checkDuplicateReactions(ResourceValidationContext ctx, ItemPath path, ObjectSynchronizationType objectSync,
Map<SynchronizationSituationType, Integer> counts) {
List<SynchronizationSituationType> duplicates = new ArrayList<>();
for (Map.Entry<SynchronizationSituationType, Integer> entry : counts.entrySet()) {
if (entry.getValue() > 1) {
duplicates.add(entry.getKey());
}
}
if (!duplicates.isEmpty()) {
ctx.validationResult.add(Issue.Severity.WARNING, CAT_SYNCHRONIZATION, C_DUPLICATE_REACTIONS,
getString(ctx.bundle, CLASS_DOT + C_DUPLICATE_REACTIONS, getName(objectSync), format(duplicates)),
ctx.resourceRef, path);
}
}
private void checkMissingReactions(ResourceValidationContext ctx, ItemPath path, ObjectSynchronizationType objectSync,
Map<SynchronizationSituationType, Integer> counts, Collection<SynchronizationSituationType> situations) {
List<SynchronizationSituationType> missing = new ArrayList<>(situations);
missing.removeAll(counts.keySet());
if (!missing.isEmpty()) {
ctx.validationResult.add(Issue.Severity.INFO, CAT_SYNCHRONIZATION, C_NO_REACTION,
getString(ctx.bundle, CLASS_DOT + C_NO_REACTION, getName(objectSync), format(missing)),
ctx.resourceRef, path);
}
}
private String getName(ObjectSynchronizationType objectSync) {
StringBuilder sb = new StringBuilder();
if (objectSync.getName() != null) {
sb.append(objectSync.getName());
sb.append(" (");
}
sb.append("kind: ");
sb.append(fillDefault(objectSync.getKind()));
sb.append(", intent: ");
sb.append(fillDefault(objectSync.getIntent()));
if (objectSync.getName() != null) {
sb.append(")");
}
return sb.toString();
}
private String getName(ResourceObjectTypeDefinitionType objectType) {
StringBuilder sb = new StringBuilder();
if (objectType.getDisplayName() != null) {
sb.append(objectType.getDisplayName());
sb.append(" (");
}
sb.append("kind: ");
sb.append(fillDefault(objectType.getKind()));
sb.append(", intent: ");
sb.append(fillDefault(objectType.getIntent()));
if (objectType.getDisplayName() != null) {
sb.append(")");
}
return sb.toString();
}
@NotNull
private String getString(ResourceBundle bundle, String key, Object... parameters) {
final String resolvedKey;
if (key != null) {
if (bundle.containsKey(key)) {
resolvedKey = bundle.getString(key);
} else {
resolvedKey = key;
}
} else {
resolvedKey = "";
}
final MessageFormat format = new MessageFormat(resolvedKey, bundle.getLocale());
return format.format(parameters);
}
// PrettyPrinter output is too verbose/confusing
// TODO move to some standard class (but PrettyPrinter is questionable)
public static String prettyPrintUsingStandardPrefix(Collection<QName> names) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (QName name : names) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(prettyPrintUsingStandardPrefix(name));
}
return sb.toString();
}
// TODO check if there's not any standard list of usual prefixes (I'm quite sure there is)
public static String prettyPrintUsingStandardPrefix(QName name) {
if (name == null) {
return null;
}
String ns = name.getNamespaceURI();
if (SchemaConstants.NS_C.equals(ns)) {
return "c:" + name.getLocalPart();
} else if (MidPointConstants.NS_RI.equals(ns)) {
return "ri:" + name.getLocalPart();
} else if (SchemaConstantsGenerated.NS_ICF_SCHEMA.equals(ns)) {
return "icfs:" + name.getLocalPart();
} else if (SchemaConstantsGenerated.NS_ICF_SCHEMA.equals(ns)) {
return "icfs:" + name.getLocalPart();
} else {
return null;
}
}
}