/** * Copyright (c) 2015-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.provisioning.impl; import java.util.ArrayList; import java.util.Collection; import javax.xml.namespace.QName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; 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.PrismContainer; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.query.AndFilter; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.QueryJaxbConvertor; import com.evolveum.midpoint.provisioning.api.GenericConnectorException; import com.evolveum.midpoint.provisioning.ucf.api.AttributesToReturn; import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.processor.ResourceAttribute; import com.evolveum.midpoint.schema.processor.ResourceObjectIdentification; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectQueryUtil; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; 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.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectReferenceResolutionFrequencyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType; /** * @author semancik * */ @Component public class ResourceObjectReferenceResolver { private static final Trace LOGGER = TraceManager.getTrace(ResourceObjectReferenceResolver.class); @Autowired(required = true) private PrismContext prismContext; @Autowired(required = true) @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired(required = true) @Qualifier("shadowCacheProvisioner") private ShadowCache shadowCache; @Autowired(required = true) private ShadowManager shadowManager; PrismObject<ShadowType> resolve(ProvisioningContext ctx, ResourceObjectReferenceType resourceObjectReference, QName objectClass, final String desc, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { if (resourceObjectReference == null) { return null; } ObjectReferenceType shadowRef = resourceObjectReference.getShadowRef(); if (shadowRef != null && shadowRef.getOid() != null) { if (resourceObjectReference.getResolutionFrequency() == null || resourceObjectReference.getResolutionFrequency() == ResourceObjectReferenceResolutionFrequencyType.ONCE) { PrismObject<ShadowType> shadow = repositoryService.getObject(ShadowType.class, shadowRef.getOid(), null, result); return shadow; } } else if (resourceObjectReference.getResolutionFrequency() == ResourceObjectReferenceResolutionFrequencyType.NEVER) { throw new ObjectNotFoundException("No shadowRef OID in "+desc+" and resolution frequency set to NEVER"); } if (resourceObjectReference.getObjectClass() != null) { objectClass = resourceObjectReference.getObjectClass(); if (objectClass.getNamespaceURI() == null) { objectClass = new QName(ResourceTypeUtil.getResourceNamespace(ctx.getResource()), objectClass.getLocalPart()); } } ProvisioningContext subctx = ctx.spawn(objectClass); // Use "raw" definitions from the original schema to avoid endless loops subctx.setUseRefinedDefinition(false); subctx.assertDefinition(); ObjectQuery refQuery = QueryJaxbConvertor.createObjectQuery(ShadowType.class, resourceObjectReference.getFilter(), prismContext); ObjectFilter baseFilter = ObjectQueryUtil.createResourceAndObjectClassFilter(ctx.getResource().getOid(), objectClass, prismContext); ObjectFilter filter = AndFilter.createAnd(baseFilter, refQuery.getFilter()); ObjectQuery query = ObjectQuery.createObjectQuery(filter); // TODO: implement "repo" search strategies Collection<SelectorOptions<GetOperationOptions>> options = null; final Holder<ShadowType> shadowHolder = new Holder<>(); ShadowHandler<ShadowType> handler = new ShadowHandler<ShadowType>() { @Override public boolean handle(ShadowType shadow) { if (shadowHolder.getValue() != null) { throw new IllegalStateException("More than one search results for " + desc); } shadowHolder.setValue(shadow); return true; } }; shadowCache.searchObjectsIterative(subctx, query, options, handler, true, result); // TODO: implement storage of OID (ONCE search frequency) ShadowType shadowType = shadowHolder.getValue(); return shadowType==null?null:shadowType.asPrismObject(); } /** * Resolve primary identifier from a collection of identifiers that may contain only secondary identifiers. */ Collection<? extends ResourceAttribute<?>> resolvePrimaryIdentifier(ProvisioningContext ctx, Collection<? extends ResourceAttribute<?>> identifiers, final String desc, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { if (identifiers == null) { return null; } Collection<ResourceAttribute<?>> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(identifiers, ctx.getObjectClassDefinition()); PrismObject<ShadowType> repoShadow = shadowManager.lookupShadowBySecondaryIdentifiers(ctx, secondaryIdentifiers, result); if (repoShadow == null) { return null; } PrismContainer<Containerable> attributesContainer = repoShadow.findContainer(ShadowType.F_ATTRIBUTES); if (attributesContainer == null) { return null; } RefinedObjectClassDefinition ocDef = ctx.getObjectClassDefinition(); Collection primaryIdentifiers = new ArrayList<>(); for (PrismProperty<?> property: attributesContainer.getValue().getProperties()) { if (ocDef.isPrimaryIdentifier(property.getElementName())) { RefinedAttributeDefinition<?> attrDef = ocDef.findAttributeDefinition(property.getElementName()); ResourceAttribute<?> primaryIdentifier = new ResourceAttribute<>(property.getElementName(), attrDef, prismContext); primaryIdentifier.setRealValue(property.getRealValue()); primaryIdentifiers.add(primaryIdentifier); } } LOGGER.trace("Resolved identifiers {} to primary identifiers {} (object class {})", identifiers, primaryIdentifiers, ocDef); return primaryIdentifiers; } /** * Resolve primary identifier from a collection of identifiers that may contain only secondary identifiers. */ private ResourceObjectIdentification resolvePrimaryIdentifiers(ProvisioningContext ctx, ResourceObjectIdentification identification, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { if (identification == null) { return identification; } if (identification.hasPrimaryIdentifiers()) { return identification; } Collection<ResourceAttribute<?>> secondaryIdentifiers = (Collection<ResourceAttribute<?>>) identification.getSecondaryIdentifiers(); PrismObject<ShadowType> repoShadow = shadowManager.lookupShadowBySecondaryIdentifiers(ctx, secondaryIdentifiers, result); if (repoShadow == null) { // TODO: we should attempt resource search here throw new ObjectNotFoundException("No repository shadow for "+secondaryIdentifiers+", cannot resolve identifiers"); } PrismContainer<Containerable> attributesContainer = repoShadow.findContainer(ShadowType.F_ATTRIBUTES); if (attributesContainer == null) { throw new SchemaException("No attributes in "+repoShadow+", cannot resolve identifiers "+secondaryIdentifiers); } RefinedObjectClassDefinition ocDef = ctx.getObjectClassDefinition(); Collection primaryIdentifiers = new ArrayList<>(); for (PrismProperty<?> property: attributesContainer.getValue().getProperties()) { if (ocDef.isPrimaryIdentifier(property.getElementName())) { RefinedAttributeDefinition<?> attrDef = ocDef.findAttributeDefinition(property.getElementName()); ResourceAttribute<?> primaryIdentifier = new ResourceAttribute<>(property.getElementName(), attrDef, prismContext); primaryIdentifier.setRealValue(property.getRealValue()); primaryIdentifiers.add(primaryIdentifier); } } LOGGER.trace("Resolved {} to primary identifiers {} (object class {})", identification, primaryIdentifiers, ocDef); return new ResourceObjectIdentification(identification.getObjectClassDefinition(), primaryIdentifiers, identification.getSecondaryIdentifiers()); } public PrismObject<ShadowType> fetchResourceObject(ProvisioningContext ctx, Collection<? extends ResourceAttribute<?>> identifiers, AttributesToReturn attributesToReturn, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, SecurityViolationException, ConfigurationException { ResourceType resource = ctx.getResource(); ConnectorInstance connector = ctx.getConnector(ReadCapabilityType.class, parentResult); RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition(); try { if (!ResourceTypeUtil.isReadCapabilityEnabled(resource)){ throw new UnsupportedOperationException("Resource does not support 'read' operation"); } ResourceObjectIdentification identification = ResourceObjectIdentification.create(objectClassDefinition, identifiers); identification = resolvePrimaryIdentifiers(ctx, identification, parentResult); identification.validatePrimaryIdenfiers(); return connector.fetchObject(ShadowType.class, identification, attributesToReturn, ctx, parentResult); } catch (ObjectNotFoundException e) { parentResult.recordFatalError( "Object not found. Identifiers: " + identifiers + ". Reason: " + e.getMessage(), e); throw new ObjectNotFoundException("Object not found. identifiers=" + identifiers + ", objectclass="+ PrettyPrinter.prettyPrint(objectClassDefinition.getTypeName())+": " + e.getMessage(), e); } catch (CommunicationException e) { parentResult.recordFatalError("Error communication with the connector " + connector + ": " + e.getMessage(), e); throw e; } catch (GenericFrameworkException e) { parentResult.recordFatalError( "Generic error in the connector " + connector + ". Reason: " + e.getMessage(), e); throw new GenericConnectorException("Generic error in the connector " + connector + ". Reason: " + e.getMessage(), e); } catch (SchemaException ex) { parentResult.recordFatalError("Can't get resource object, schema error: " + ex.getMessage(), ex); throw ex; } catch (ConfigurationException e) { parentResult.recordFatalError(e); throw e; } } }