Java tutorial
/* * 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.provisioning.ucf.impl.connid; import static com.evolveum.midpoint.provisioning.ucf.impl.connid.ConnIdUtil.processConnIdException; import java.util.*; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.query.ObjectPaging; import com.evolveum.midpoint.prism.query.OrderDirection; import com.evolveum.midpoint.schema.SearchResultMetadata; import com.evolveum.midpoint.schema.processor.*; import com.evolveum.midpoint.schema.statistics.ConnectorOperationalStatus; import com.evolveum.midpoint.schema.statistics.ProvisioningOperation; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.AddRemoveAttributeValuesCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PagedSearchCapabilityType; import com.evolveum.prism.xml.ns._public.query_3.OrderDirectionType; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.identityconnectors.common.pooling.ObjectPoolConfiguration; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.api.APIConfiguration; import org.identityconnectors.framework.api.ConnectorFacade; import org.identityconnectors.framework.api.ConnectorFacadeFactory; import org.identityconnectors.framework.api.ConnectorInfo; import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.api.operations.CreateApiOp; import org.identityconnectors.framework.api.operations.DeleteApiOp; import org.identityconnectors.framework.api.operations.GetApiOp; import org.identityconnectors.framework.api.operations.SchemaApiOp; import org.identityconnectors.framework.api.operations.ScriptOnConnectorApiOp; import org.identityconnectors.framework.api.operations.ScriptOnResourceApiOp; import org.identityconnectors.framework.api.operations.SearchApiOp; import org.identityconnectors.framework.api.operations.SyncApiOp; import org.identityconnectors.framework.api.operations.TestApiOp; import org.identityconnectors.framework.api.operations.UpdateApiOp; import org.identityconnectors.framework.common.exceptions.AlreadyExistsException; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeBuilder; import org.identityconnectors.framework.common.objects.AttributeInfo; import org.identityconnectors.framework.common.objects.AttributeInfo.Flags; import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.ObjectClassInfo; import org.identityconnectors.framework.common.objects.OperationOptionInfo; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.OperationOptionsBuilder; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.PredefinedAttributes; import org.identityconnectors.framework.common.objects.QualifiedUid; import org.identityconnectors.framework.common.objects.ResultsHandler; import org.identityconnectors.framework.common.objects.Schema; import org.identityconnectors.framework.common.objects.ScriptContext; import org.identityconnectors.framework.common.objects.SearchResult; import org.identityconnectors.framework.common.objects.SortKey; import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncDeltaType; import org.identityconnectors.framework.common.objects.SyncResultsHandler; import org.identityconnectors.framework.common.objects.SyncToken; import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.framework.common.objects.filter.Filter; import org.identityconnectors.framework.impl.api.APIConfigurationImpl; import org.identityconnectors.framework.impl.api.local.LocalConnectorInfoImpl; import org.identityconnectors.framework.impl.api.local.ObjectPool; import org.identityconnectors.framework.impl.api.local.ObjectPool.Statistics; import org.identityconnectors.framework.impl.api.local.operations.ConnectorOperationalContext; import org.identityconnectors.framework.spi.Connector; import org.identityconnectors.framework.spi.PoolableConnector; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.match.DistinguishedNameMatchingRule; import com.evolveum.midpoint.prism.match.StringIgnoreCaseMatchingRule; import com.evolveum.midpoint.prism.match.UuidMatchingRule; import com.evolveum.midpoint.prism.match.XmlMatchingRule; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.schema.PrismSchema; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.provisioning.ucf.api.AttributesToReturn; import com.evolveum.midpoint.provisioning.ucf.api.Change; import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; import com.evolveum.midpoint.provisioning.ucf.api.ExecuteProvisioningScriptOperation; import com.evolveum.midpoint.provisioning.ucf.api.ExecuteScriptArgument; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.provisioning.ucf.api.Operation; import com.evolveum.midpoint.provisioning.ucf.api.PasswordChangeOperation; import com.evolveum.midpoint.provisioning.ucf.api.PropertyModificationOperation; import com.evolveum.midpoint.provisioning.ucf.api.ShadowResultHandler; import com.evolveum.midpoint.provisioning.ucf.impl.connid.query.FilterInterpreter; import com.evolveum.midpoint.schema.CapabilityUtil; import com.evolveum.midpoint.schema.constants.ConnectorTestOperation; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.result.AsynchronousOperationResult; import com.evolveum.midpoint.schema.result.AsynchronousOperationReturnValue; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.ActivationUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.StateReporter; import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.BeforeAfterType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CriticalityType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LockoutStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ProvisioningScriptHostType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationLockoutStatusCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationStatusCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationValidityCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.AuxiliaryObjectClassesCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CreateCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.DeleteCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.LiveSyncCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PasswordCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.SchemaCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ScriptCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ScriptCapabilityType.Host; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.TestConnectionCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.UpdateCapabilityType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; /** * Implementation of ConnectorInstance for ConnId connectors. * <p/> * This class implements the ConnectorInstance interface. The methods are * converting the data from the "midPoint semantics" as seen by the * ConnectorInstance interface to the "ConnId semantics" as seen by the ConnId * framework. * * @author Radovan Semancik */ public class ConnectorInstanceConnIdImpl implements ConnectorInstance { private static final com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ObjectFactory CAPABILITY_OBJECT_FACTORY = new com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ObjectFactory(); private static final Trace LOGGER = TraceManager.getTrace(ConnectorInstanceConnIdImpl.class); ConnectorInfo cinfo; ConnectorType connectorType; ConnectorFacade connIdConnectorFacade; String resourceSchemaNamespace; private PrismSchema connectorSchema; private APIConfiguration apiConfig = null; Protector protector; PrismContext prismContext; private ConnIdNameMapper connIdNameMapper; private ConnIdConvertor connIdConvertor; private ResourceSchema resourceSchema = null; private Collection<Object> capabilities = null; private String description; private boolean caseIgnoreAttributeNames = false; private Boolean legacySchema = null; private boolean supportsReturnDefaultAttributes = false; ConnectorInstanceConnIdImpl(ConnectorInfo connectorInfo, ConnectorType connectorType, String schemaNamespace, PrismSchema connectorSchema, Protector protector, PrismContext prismContext) { this.cinfo = connectorInfo; this.connectorType = connectorType; this.resourceSchemaNamespace = schemaNamespace; this.connectorSchema = connectorSchema; this.protector = protector; this.prismContext = prismContext; connIdNameMapper = new ConnIdNameMapper(schemaNamespace); connIdConvertor = new ConnIdConvertor(protector, resourceSchemaNamespace); connIdConvertor.setIcfNameMapper(connIdNameMapper); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getSchemaNamespace() { return resourceSchemaNamespace; } public void setResourceSchema(ResourceSchema resourceSchema) { this.resourceSchema = resourceSchema; connIdNameMapper.setResourceSchema(resourceSchema); } public void resetResourceSchema() { setResourceSchema(null); } @Override public void configure(PrismContainerValue<?> configurationOriginal, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException { OperationResult result = parentResult.createSubresult(ConnectorInstance.OPERATION_CONFIGURE); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Configuring connector {}, provided configuration:\n{}", connectorType, configurationOriginal.debugDump(1)); } try { // Get default configuration for the connector. This is important, // as it contains types of connector configuration properties. // Make sure that the proper configuration schema is applied. This // will cause that all the "raw" elements are parsed PrismContainerValue<?> configurationCloned = configurationOriginal.clone(); configurationCloned.applyDefinition(getConfigurationContainerDefinition()); ConnIdConfigurationTransformer configTransformer = new ConnIdConfigurationTransformer(connectorType, cinfo, protector); // Transform XML configuration from the resource to the ConnId connector configuration try { apiConfig = configTransformer.transformConnectorConfiguration(configurationCloned); } catch (SchemaException e) { result.recordFatalError(e.getMessage(), e); throw e; } if (LOGGER.isTraceEnabled()) { LOGGER.trace("Configuring connector {}, transformed configuration:", connectorType); for (String propName : apiConfig.getConfigurationProperties().getPropertyNames()) { LOGGER.trace("P: {} = {}", propName, apiConfig.getConfigurationProperties().getProperty(propName).getValue()); } } // Create new connector instance using the transformed configuration connIdConnectorFacade = ConnectorFacadeFactory.getInstance().newInstance(apiConfig); result.recordSuccess(); PrismProperty<Boolean> legacySchemaConfigProperty = configurationCloned .findProperty(new QName(SchemaConstants.NS_ICF_CONFIGURATION, ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_LEGACY_SCHEMA_XML_ELEMENT_NAME)); if (legacySchemaConfigProperty != null) { legacySchema = legacySchemaConfigProperty.getRealValue(); } LOGGER.trace("Legacy schema (config): {}", legacySchema); } catch (Throwable ex) { Throwable midpointEx = processConnIdException(ex, this, result); result.computeStatus("Removing attribute values failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof ConfigurationException) { throw (ConfigurationException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } } private PrismContainerDefinition<?> getConfigurationContainerDefinition() throws SchemaException { QName configContainerQName = new QName(connectorType.getNamespace(), ResourceType.F_CONNECTOR_CONFIGURATION.getLocalPart()); PrismContainerDefinition<?> configContainerDef = connectorSchema .findContainerDefinitionByElementName(configContainerQName); if (configContainerDef == null) { throw new SchemaException("No definition of container " + configContainerQName + " in configuration schema for connector " + this); } return configContainerDef; } private QName icfAttributeInfoToMatchingRule(AttributeInfo attributeInfo) { String icfSubtype = attributeInfo.getSubtype(); if (icfSubtype == null) { return null; } if (AttributeInfo.Subtypes.STRING_CASE_IGNORE.toString().equals(icfSubtype)) { return StringIgnoreCaseMatchingRule.NAME; } if (AttributeInfo.Subtypes.STRING_LDAP_DN.toString().equals(icfSubtype)) { return DistinguishedNameMatchingRule.NAME; } if (AttributeInfo.Subtypes.STRING_XML.toString().equals(icfSubtype)) { return XmlMatchingRule.NAME; } if (AttributeInfo.Subtypes.STRING_UUID.toString().equals(icfSubtype)) { return UuidMatchingRule.NAME; } LOGGER.debug("Unknown subtype {} defined for attribute {}, ignoring (no matching rule definition)", icfSubtype, attributeInfo.getName()); return null; } @Override public ConnectorOperationalStatus getOperationalStatus() throws ObjectNotFoundException { if (!(cinfo instanceof LocalConnectorInfoImpl)) { LOGGER.trace("Cannot get operational status of a remote connector {}", connectorType); return null; } if (apiConfig == null) { LOGGER.trace("Cannot get operational status of a connector {}: connector not yet configured", connectorType); throw new IllegalStateException("Connector " + connectorType + " not yet configured"); } ConnectorOperationalStatus status = new ConnectorOperationalStatus(); ConnectorOperationalContext connectorOperationalContext = new ConnectorOperationalContext( (LocalConnectorInfoImpl) cinfo, (APIConfigurationImpl) apiConfig); Class<? extends Connector> connectorClass = connectorOperationalContext.getConnectorClass(); if (connectorClass != null) { status.setConnectorClassName(connectorClass.getName()); } ObjectPoolConfiguration poolConfiguration = apiConfig.getConnectorPoolConfiguration(); if (poolConfiguration != null) { status.setPoolConfigMaxSize(poolConfiguration.getMaxObjects()); status.setPoolConfigMinIdle(poolConfiguration.getMinIdle()); status.setPoolConfigMaxIdle(poolConfiguration.getMaxIdle()); status.setPoolConfigWaitTimeout(poolConfiguration.getMaxWait()); status.setPoolConfigMinEvictableIdleTime(poolConfiguration.getMinEvictableIdleTimeMillis()); } ObjectPool<PoolableConnector> pool = connectorOperationalContext.getPool(); if (pool != null) { Statistics poolStats = pool.getStatistics(); if (poolStats != null) { status.setPoolStatusNumActive(poolStats.getNumActive()); status.setPoolStatusNumIdle(poolStats.getNumIdle()); } } return status; } @Override public void initialize(ResourceSchema resourceSchema, Collection<Object> capabilities, boolean caseIgnoreAttributeNames, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ConfigurationException { // Result type for this operation OperationResult result = parentResult.createSubresult(ConnectorInstance.OPERATION_INITIALIZE); result.addContext("connector", connectorType); result.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, ConnectorFactoryConnIdImpl.class); if (connIdConnectorFacade == null) { result.recordFatalError("Attempt to use unconfigured connector"); throw new IllegalStateException( "Attempt to use unconfigured connector " + ObjectTypeUtil.toShortString(connectorType)); } setResourceSchema(resourceSchema); this.capabilities = capabilities; this.caseIgnoreAttributeNames = caseIgnoreAttributeNames; if (resourceSchema != null && legacySchema == null) { legacySchema = detectLegacySchema(resourceSchema); } if (resourceSchema == null || capabilities == null) { try { boolean supportsSchema = processOperationCapabilities(result); if (supportsSchema) { retrieveResourceSchema(null, result); } } catch (CommunicationException ex) { // This is in fact fatal. There is not schema. Not even the pre-cached one. // The connector will not be able to work. result.recordFatalError(ex); throw ex; } catch (ConfigurationException ex) { result.recordFatalError(ex); throw ex; } catch (GenericFrameworkException ex) { result.recordFatalError(ex); throw ex; } } result.recordSuccess(); } @Override public ResourceSchema fetchResourceSchema(List<QName> generateObjectClasses, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ConfigurationException { // Result type for this operation OperationResult result = parentResult .createSubresult(ConnectorInstance.class.getName() + ".fetchResourceSchema"); result.addContext("connector", connectorType); try { boolean supportsSchema = processOperationCapabilities(result); if (!supportsSchema) { result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Connector does not support schema"); LOGGER.trace("Connector instance {} does not support schema, skipping", this); return null; } retrieveResourceSchema(generateObjectClasses, result); } catch (CommunicationException ex) { result.recordFatalError(ex); throw ex; } catch (ConfigurationException ex) { result.recordFatalError(ex); throw ex; } catch (GenericFrameworkException ex) { result.recordFatalError(ex); throw ex; } if (resourceSchema == null) { result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Connector does not support schema"); } else { result.recordSuccess(); } return resourceSchema; } @Override public Collection<Object> fetchCapabilities(OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ConfigurationException { // Result type for this operation OperationResult result = parentResult .createMinorSubresult(ConnectorInstance.class.getName() + ".fetchCapabilities"); result.addContext("connector", connectorType); try { boolean supportsSchema = processOperationCapabilities(result); if (supportsSchema) { LOGGER.trace("Connector instance {} does not support schema, skipping", this); // we need to get schema to figure out all the capabilities retrieveResourceSchema(null, result); } } catch (CommunicationException ex) { result.recordFatalError(ex); throw ex; } catch (ConfigurationException ex) { result.recordFatalError(ex); throw ex; } catch (GenericFrameworkException ex) { result.recordFatalError(ex); throw ex; } result.recordSuccess(); return capabilities; } private boolean processOperationCapabilities(OperationResult parentResult) { capabilities = new ArrayList<>(); // Create capabilities from supported connector operations InternalMonitor.recordConnectorOperation("getSupportedOperations"); Set<Class<? extends APIOperation>> supportedOperations = connIdConnectorFacade.getSupportedOperations(); LOGGER.trace("Connector supported operations: {}", supportedOperations); boolean supportsSchema = false; if (supportedOperations.contains(SchemaApiOp.class)) { SchemaCapabilityType capSchema = new SchemaCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createSchema(capSchema)); supportsSchema = true; } if (supportedOperations.contains(SyncApiOp.class)) { LiveSyncCapabilityType capSync = new LiveSyncCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createLiveSync(capSync)); } if (supportedOperations.contains(TestApiOp.class)) { TestConnectionCapabilityType capTest = new TestConnectionCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createTestConnection(capTest)); } if (supportedOperations.contains(CreateApiOp.class)) { CreateCapabilityType capCreate = new CreateCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createCreate(capCreate)); } if (supportedOperations.contains(GetApiOp.class) || supportedOperations.contains(SearchApiOp.class)) { ReadCapabilityType capRead = new ReadCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createRead(capRead)); } if (supportedOperations.contains(UpdateApiOp.class)) { UpdateCapabilityType capUpdate = new UpdateCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createUpdate(capUpdate)); } if (supportedOperations.contains(DeleteApiOp.class)) { DeleteCapabilityType capDelete = new DeleteCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createDelete(capDelete)); } if (supportedOperations.contains(ScriptOnResourceApiOp.class) || supportedOperations.contains(ScriptOnConnectorApiOp.class)) { ScriptCapabilityType capScript = new ScriptCapabilityType(); if (supportedOperations.contains(ScriptOnResourceApiOp.class)) { Host host = new Host(); host.setType(ProvisioningScriptHostType.RESOURCE); capScript.getHost().add(host); // language is unknown here } if (supportedOperations.contains(ScriptOnConnectorApiOp.class)) { Host host = new Host(); host.setType(ProvisioningScriptHostType.CONNECTOR); capScript.getHost().add(host); // language is unknown here } capabilities.add(CAPABILITY_OBJECT_FACTORY.createScript(capScript)); } return supportsSchema; } private void retrieveResourceSchema(List<QName> generateObjectClasses, OperationResult parentResult) throws CommunicationException, ConfigurationException, GenericFrameworkException { // Connector operation cannot create result for itself, so we need to // create result for it OperationResult icfResult = parentResult.createSubresult(ConnectorFacade.class.getName() + ".schema"); icfResult.addContext("connector", connIdConnectorFacade.getClass()); org.identityconnectors.framework.common.objects.Schema icfSchema = null; try { // Fetch the schema from the connector (which actually gets that // from the resource). InternalMonitor.recordConnectorOperation("schema"); // TODO have context present //recordIcfOperationStart(reporter, ProvisioningOperation.ICF_GET_SCHEMA, null); icfSchema = connIdConnectorFacade.schema(); //recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET_SCHEMA, null); icfResult.recordSuccess(); } catch (UnsupportedOperationException ex) { //recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET_SCHEMA, null, ex); // The connector does no support schema() operation. icfResult.recordStatus(OperationResultStatus.NOT_APPLICABLE, ex.getMessage()); resetResourceSchema(); return; } catch (Throwable ex) { //recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET_SCHEMA, null, ex); // conditions. // Therefore this kind of heavy artillery is necessary. // ICF interface does not specify exceptions or other error // TODO maybe we can try to catch at least some specific exceptions Throwable midpointEx = processConnIdException(ex, this, icfResult); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { icfResult.recordFatalError(midpointEx.getMessage(), midpointEx); throw (CommunicationException) midpointEx; } else if (midpointEx instanceof ConfigurationException) { icfResult.recordFatalError(midpointEx.getMessage(), midpointEx); throw (ConfigurationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { icfResult.recordFatalError(midpointEx.getMessage(), midpointEx); throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof RuntimeException) { icfResult.recordFatalError(midpointEx.getMessage(), midpointEx); throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { icfResult.recordFatalError(midpointEx.getMessage(), midpointEx); throw (Error) midpointEx; } else { icfResult.recordFatalError(midpointEx.getMessage(), midpointEx); throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } if (icfSchema == null) { icfResult.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Null schema returned"); resetResourceSchema(); return; } parseResourceSchema(icfSchema, generateObjectClasses); } private void parseResourceSchema(org.identityconnectors.framework.common.objects.Schema icfSchema, List<QName> generateObjectClasses) { AttributeInfo passwordAttributeInfo = null; AttributeInfo enableAttributeInfo = null; AttributeInfo enableDateAttributeInfo = null; AttributeInfo disableDateAttributeInfo = null; AttributeInfo lockoutAttributeInfo = null; AttributeInfo auxiliaryObjectClasseAttributeInfo = null; // New instance of midPoint schema object setResourceSchema(new ResourceSchemaImpl(getSchemaNamespace(), prismContext)); if (legacySchema == null) { legacySchema = detectLegacySchema(icfSchema); } LOGGER.trace("Converting resource schema (legacy mode: {})", legacySchema); LOGGER.trace("Generating object classes: {}", generateObjectClasses); Set<ObjectClassInfo> objectClassInfoSet = icfSchema.getObjectClassInfo(); // Let's convert every objectclass in the ConnId schema ... for (ObjectClassInfo objectClassInfo : objectClassInfoSet) { // "Flat" ConnId object class names needs to be mapped to QNames QName objectClassXsdName = connIdNameMapper.objectClassToQname( new ObjectClass(objectClassInfo.getType()), getSchemaNamespace(), legacySchema); if (!shouldBeGenerated(generateObjectClasses, objectClassXsdName)) { LOGGER.trace("Skipping object class {} ({})", objectClassInfo.getType(), objectClassXsdName); continue; } LOGGER.trace("Converting object class {} ({})", objectClassInfo.getType(), objectClassXsdName); // ResourceObjectDefinition is a midPpoint way how to represent an // object class. // The important thing here is the last "type" parameter // (objectClassXsdName). The rest is more-or-less cosmetics. ObjectClassComplexTypeDefinition ocDef = ((ResourceSchemaImpl) resourceSchema) .createObjectClassDefinition(objectClassXsdName); // The __ACCOUNT__ objectclass in ConnId is a default account // objectclass. So mark it appropriately. if (ObjectClass.ACCOUNT_NAME.equals(objectClassInfo.getType())) { ((ObjectClassComplexTypeDefinitionImpl) ocDef).setKind(ShadowKindType.ACCOUNT); ((ObjectClassComplexTypeDefinitionImpl) ocDef).setDefaultInAKind(true); } ResourceAttributeDefinition<String> uidDefinition = null; ResourceAttributeDefinition<String> nameDefinition = null; boolean hasUidDefinition = false; int displayOrder = ConnectorFactoryConnIdImpl.ATTR_DISPLAY_ORDER_START; // Let's iterate over all attributes in this object class ... Set<AttributeInfo> attributeInfoSet = objectClassInfo.getAttributeInfo(); for (AttributeInfo attributeInfo : attributeInfoSet) { String icfName = attributeInfo.getName(); if (OperationalAttributes.PASSWORD_NAME.equals(icfName)) { // This attribute will not go into the schema // instead a "password" capability is used passwordAttributeInfo = attributeInfo; // Skip this attribute, capability is sufficient continue; } if (OperationalAttributes.ENABLE_NAME.equals(icfName)) { enableAttributeInfo = attributeInfo; // Skip this attribute, capability is sufficient continue; } if (OperationalAttributes.ENABLE_DATE_NAME.equals(icfName)) { enableDateAttributeInfo = attributeInfo; // Skip this attribute, capability is sufficient continue; } if (OperationalAttributes.DISABLE_DATE_NAME.equals(icfName)) { disableDateAttributeInfo = attributeInfo; // Skip this attribute, capability is sufficient continue; } if (OperationalAttributes.LOCK_OUT_NAME.equals(icfName)) { lockoutAttributeInfo = attributeInfo; // Skip this attribute, capability is sufficient continue; } if (PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME.equals(icfName)) { auxiliaryObjectClasseAttributeInfo = attributeInfo; // Skip this attribute, capability is sufficient continue; } String processedAttributeName = icfName; if ((Name.NAME.equals(icfName) || Uid.NAME.equals(icfName)) && attributeInfo.getNativeName() != null) { processedAttributeName = attributeInfo.getNativeName(); } QName attrXsdName = connIdNameMapper.convertAttributeNameToQName(processedAttributeName, ocDef); QName attrXsdType = ConnIdUtil.icfTypeToXsdType(attributeInfo.getType(), false); if (LOGGER.isTraceEnabled()) { LOGGER.trace(" attr conversion ConnId: {}({}) -> XSD: {}({})", icfName, attributeInfo.getType().getSimpleName(), PrettyPrinter.prettyPrint(attrXsdName), PrettyPrinter.prettyPrint(attrXsdType)); } // Create ResourceObjectAttributeDefinition, which is midPoint // way how to express attribute schema. ResourceAttributeDefinitionImpl attrDef = new ResourceAttributeDefinitionImpl(attrXsdName, attrXsdType, prismContext); attrDef.setMatchingRuleQName(icfAttributeInfoToMatchingRule(attributeInfo)); if (Name.NAME.equals(icfName)) { nameDefinition = attrDef; if (uidDefinition != null && attrXsdName.equals(uidDefinition.getName())) { attrDef.setDisplayOrder(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_ORDER); uidDefinition = attrDef; hasUidDefinition = true; } else { if (attributeInfo.getNativeName() == null) { // Set a better display name for __NAME__. The "name" is s very // overloaded term, so let's try to make things // a bit clearer attrDef.setDisplayName(ConnectorFactoryConnIdImpl.ICFS_NAME_DISPLAY_NAME); } attrDef.setDisplayOrder(ConnectorFactoryConnIdImpl.ICFS_NAME_DISPLAY_ORDER); } } else if (Uid.NAME.equals(icfName)) { // UID can be the same as other attribute ResourceAttributeDefinition existingDefinition = ocDef.findAttributeDefinition(attrXsdName); if (existingDefinition != null) { hasUidDefinition = true; ((ResourceAttributeDefinitionImpl) existingDefinition) .setDisplayOrder(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_ORDER); uidDefinition = existingDefinition; continue; } else { uidDefinition = attrDef; if (attributeInfo.getNativeName() == null) { attrDef.setDisplayName(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_NAME); } attrDef.setDisplayOrder(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_ORDER); } } else { // Check conflict with UID definition if (uidDefinition != null && attrXsdName.equals(uidDefinition.getName())) { attrDef.setDisplayOrder(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_ORDER); uidDefinition = attrDef; hasUidDefinition = true; } else { attrDef.setDisplayOrder(displayOrder); displayOrder += ConnectorFactoryConnIdImpl.ATTR_DISPLAY_ORDER_INCREMENT; } } attrDef.setNativeAttributeName(attributeInfo.getNativeName()); attrDef.setFrameworkAttributeName(icfName); // Now we are going to process flags such as optional and // multi-valued Set<Flags> flagsSet = attributeInfo.getFlags(); // System.out.println(flagsSet); attrDef.setMinOccurs(0); attrDef.setMaxOccurs(1); boolean canCreate = true; boolean canUpdate = true; boolean canRead = true; for (Flags flags : flagsSet) { if (flags == Flags.REQUIRED) { attrDef.setMinOccurs(1); } if (flags == Flags.MULTIVALUED) { attrDef.setMaxOccurs(-1); } if (flags == Flags.NOT_CREATABLE) { canCreate = false; } if (flags == Flags.NOT_READABLE) { canRead = false; } if (flags == Flags.NOT_UPDATEABLE) { canUpdate = false; } if (flags == Flags.NOT_RETURNED_BY_DEFAULT) { attrDef.setReturnedByDefault(false); } } attrDef.setCanAdd(canCreate); attrDef.setCanModify(canUpdate); attrDef.setCanRead(canRead); if (!Uid.NAME.equals(icfName)) { ((ObjectClassComplexTypeDefinitionImpl) ocDef).add(attrDef); } } if (uidDefinition == null) { // Every object has UID in ConnId, therefore add a default definition if no other was specified uidDefinition = new ResourceAttributeDefinitionImpl<>(SchemaConstants.ICFS_UID, DOMUtil.XSD_STRING, prismContext); // DO NOT make it mandatory. It must not be present on create hence it cannot be mandatory. ((ResourceAttributeDefinitionImpl) uidDefinition).setMinOccurs(0); ((ResourceAttributeDefinitionImpl) uidDefinition).setMaxOccurs(1); // Make it read-only ((ResourceAttributeDefinitionImpl) uidDefinition).setReadOnly(); // Set a default display name ((ResourceAttributeDefinitionImpl) uidDefinition) .setDisplayName(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_NAME); ((ResourceAttributeDefinitionImpl) uidDefinition) .setDisplayOrder(ConnectorFactoryConnIdImpl.ICFS_UID_DISPLAY_ORDER); // Uid is a primary identifier of every object (this is the ConnId way) } if (!hasUidDefinition) { ((ObjectClassComplexTypeDefinitionImpl) ocDef).add(uidDefinition); } ((ObjectClassComplexTypeDefinitionImpl) ocDef).addPrimaryIdentifier(uidDefinition); if (uidDefinition != nameDefinition) { ((ObjectClassComplexTypeDefinitionImpl) ocDef).addSecondaryIdentifier(nameDefinition); } // Add schema annotations ((ObjectClassComplexTypeDefinitionImpl) ocDef).setNativeObjectClass(objectClassInfo.getType()); ((ObjectClassComplexTypeDefinitionImpl) ocDef).setDisplayNameAttribute(nameDefinition.getName()); ((ObjectClassComplexTypeDefinitionImpl) ocDef).setNamingAttribute(nameDefinition.getName()); ((ObjectClassComplexTypeDefinitionImpl) ocDef).setAuxiliary(objectClassInfo.isAuxiliary()); LOGGER.trace(" ... converted object class {}: {}", objectClassInfo.getType(), ocDef); } // This is the default for all resources. // (Currently there is no way how to obtain it from the connector.) // It can be disabled manually. AddRemoveAttributeValuesCapabilityType addRemove = new AddRemoveAttributeValuesCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createAddRemoveAttributeValues(addRemove)); ActivationCapabilityType capAct = null; if (enableAttributeInfo != null) { if (capAct == null) { capAct = new ActivationCapabilityType(); } ActivationStatusCapabilityType capActStatus = new ActivationStatusCapabilityType(); capAct.setStatus(capActStatus); if (!enableAttributeInfo.isReturnedByDefault()) { capActStatus.setReturnedByDefault(false); } } if (enableDateAttributeInfo != null) { if (capAct == null) { capAct = new ActivationCapabilityType(); } ActivationValidityCapabilityType capValidFrom = new ActivationValidityCapabilityType(); capAct.setValidFrom(capValidFrom); if (!enableDateAttributeInfo.isReturnedByDefault()) { capValidFrom.setReturnedByDefault(false); } } if (disableDateAttributeInfo != null) { if (capAct == null) { capAct = new ActivationCapabilityType(); } ActivationValidityCapabilityType capValidTo = new ActivationValidityCapabilityType(); capAct.setValidTo(capValidTo); if (!disableDateAttributeInfo.isReturnedByDefault()) { capValidTo.setReturnedByDefault(false); } } if (lockoutAttributeInfo != null) { if (capAct == null) { capAct = new ActivationCapabilityType(); } ActivationLockoutStatusCapabilityType capActStatus = new ActivationLockoutStatusCapabilityType(); capAct.setLockoutStatus(capActStatus); if (!lockoutAttributeInfo.isReturnedByDefault()) { capActStatus.setReturnedByDefault(false); } } if (capAct != null) { capabilities.add(CAPABILITY_OBJECT_FACTORY.createActivation(capAct)); } if (passwordAttributeInfo != null) { CredentialsCapabilityType capCred = new CredentialsCapabilityType(); PasswordCapabilityType capPass = new PasswordCapabilityType(); if (!passwordAttributeInfo.isReturnedByDefault()) { capPass.setReturnedByDefault(false); } if (passwordAttributeInfo.isReadable()) { capPass.setReadable(true); } capCred.setPassword(capPass); capabilities.add(CAPABILITY_OBJECT_FACTORY.createCredentials(capCred)); } if (auxiliaryObjectClasseAttributeInfo != null) { AuxiliaryObjectClassesCapabilityType capAux = new AuxiliaryObjectClassesCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createAuxiliaryObjectClasses(capAux)); } boolean canPageSize = false; boolean canPageOffset = false; boolean canSort = false; for (OperationOptionInfo searchOption : icfSchema.getSupportedOptionsByOperation(SearchApiOp.class)) { switch (searchOption.getName()) { case OperationOptions.OP_PAGE_SIZE: canPageSize = true; break; case OperationOptions.OP_PAGED_RESULTS_OFFSET: canPageOffset = true; break; case OperationOptions.OP_SORT_KEYS: canSort = true; break; case OperationOptions.OP_RETURN_DEFAULT_ATTRIBUTES: supportsReturnDefaultAttributes = true; break; } } if (canPageSize || canPageOffset || canSort) { PagedSearchCapabilityType capPage = new PagedSearchCapabilityType(); capabilities.add(CAPABILITY_OBJECT_FACTORY.createPagedSearch(capPage)); } } private boolean detectLegacySchema(Schema icfSchema) { Set<ObjectClassInfo> objectClassInfoSet = icfSchema.getObjectClassInfo(); for (ObjectClassInfo objectClassInfo : objectClassInfoSet) { if (objectClassInfo.is(ObjectClass.ACCOUNT_NAME) || objectClassInfo.is(ObjectClass.GROUP_NAME)) { LOGGER.trace("This is legacy schema"); return true; } } return false; } private boolean detectLegacySchema(ResourceSchema resourceSchema) { ComplexTypeDefinition accountObjectClass = resourceSchema.findComplexTypeDefinition( new QName(getSchemaNamespace(), SchemaConstants.ACCOUNT_OBJECT_CLASS_LOCAL_NAME)); return accountObjectClass != null; } private boolean shouldBeGenerated(List<QName> generateObjectClasses, QName objectClassXsdName) { if (generateObjectClasses == null || generateObjectClasses.isEmpty()) { return true; } for (QName objClassToGenerate : generateObjectClasses) { if (objClassToGenerate.equals(objectClassXsdName)) { return true; } } return false; } private <C extends CapabilityType> C getCapability(Class<C> capClass) { if (capabilities == null) { return null; } for (Object cap : capabilities) { if (capClass.isAssignableFrom(cap.getClass())) { return (C) cap; } } return null; } @Override public PrismObject<ShadowType> fetchObject(ResourceObjectIdentification resourceObjectIdentification, AttributesToReturn attributesToReturn, StateReporter reporter, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, SchemaException, SecurityViolationException, ConfigurationException { Validate.notNull(resourceObjectIdentification, "Null primary identifiers"); ObjectClassComplexTypeDefinition objectClassDefinition = resourceObjectIdentification .getObjectClassDefinition(); // Result type for this operation OperationResult result = parentResult .createMinorSubresult(ConnectorInstance.class.getName() + ".fetchObject"); result.addArbitraryObjectAsParam("resourceObjectDefinition", objectClassDefinition); result.addArbitraryObjectAsParam("identification", resourceObjectIdentification); result.addContext("connector", connectorType); if (connIdConnectorFacade == null) { result.recordFatalError("Attempt to use unconfigured connector"); throw new IllegalStateException("Attempt to use unconfigured connector " + ObjectTypeUtil.toShortString(connectorType) + " " + description); } // Get UID from the set of identifiers Uid uid; try { uid = getUid(resourceObjectIdentification); } catch (SchemaException e) { result.recordFatalError(e); throw e; } if (uid == null) { result.recordFatalError( "Required attribute UID not found in identification set while attempting to fetch object identified by " + resourceObjectIdentification + " from " + description); throw new IllegalArgumentException( "Required attribute UID not found in identification set while attempting to fetch object identified by " + resourceObjectIdentification + " from " + description); } ObjectClass icfObjectClass = connIdNameMapper.objectClassToIcf(objectClassDefinition, getSchemaNamespace(), connectorType, legacySchema); if (icfObjectClass == null) { result.recordFatalError("Unable to determine object class from QName " + objectClassDefinition.getTypeName() + " while attempting to fetch object identified by " + resourceObjectIdentification + " from " + description); throw new IllegalArgumentException("Unable to determine object class from QName " + objectClassDefinition.getTypeName() + " while attempting to fetch object identified by " + resourceObjectIdentification + " from " + description); } OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); convertToIcfAttrsToGet(objectClassDefinition, attributesToReturn, optionsBuilder); optionsBuilder.setAllowPartialResults(true); OperationOptions options = optionsBuilder.build(); ConnectorObject co = null; try { // Invoke the ConnId connector co = fetchConnectorObject(reporter, objectClassDefinition, icfObjectClass, uid, options, result); } catch (CommunicationException ex) { result.recordFatalError(ex); // This is fatal. No point in continuing. Just re-throw the // exception. throw ex; } catch (GenericFrameworkException ex) { result.recordFatalError(ex); // This is fatal. No point in continuing. Just re-throw the // exception. throw ex; } catch (ConfigurationException ex) { result.recordFatalError(ex); throw ex; } catch (SecurityViolationException ex) { result.recordFatalError(ex); throw ex; } catch (ObjectNotFoundException ex) { result.recordFatalError("Object not found"); throw new ObjectNotFoundException("Object identified by " + resourceObjectIdentification + " (ConnId UID " + uid + "), objectClass " + objectClassDefinition.getTypeName() + " was not found in " + description); } catch (SchemaException ex) { result.recordFatalError(ex); throw ex; } catch (RuntimeException ex) { result.recordFatalError(ex); throw ex; } if (co == null) { result.recordFatalError("Object not found"); throw new ObjectNotFoundException("Object identified by " + resourceObjectIdentification + " (ConnId UID " + uid + "), objectClass " + objectClassDefinition.getTypeName() + " was not in " + description); } PrismObjectDefinition<ShadowType> shadowDefinition = toShadowDefinition(objectClassDefinition); PrismObject<ShadowType> shadow = connIdConvertor.convertToResourceObject(co, shadowDefinition, false, caseIgnoreAttributeNames, legacySchema); result.recordSuccess(); return shadow; } private PrismObjectDefinition<ShadowType> toShadowDefinition( ObjectClassComplexTypeDefinition objectClassDefinition) { ResourceAttributeContainerDefinition resourceAttributeContainerDefinition = objectClassDefinition .toResourceAttributeContainerDefinition(ShadowType.F_ATTRIBUTES); return resourceAttributeContainerDefinition.toShadowDefinition(); } /** * Returns null if nothing is found. */ private ConnectorObject fetchConnectorObject(StateReporter reporter, ObjectClassComplexTypeDefinition objectClassDefinition, ObjectClass icfObjectClass, Uid uid, OperationOptions options, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, SecurityViolationException, SchemaException, ConfigurationException { // Connector operation cannot create result for itself, so we need to // create result for it OperationResult icfResult = parentResult .createMinorSubresult(ConnectorFacade.class.getName() + ".getObject"); icfResult.addArbitraryObjectAsParam("objectClass", icfObjectClass); icfResult.addParam("uid", uid.getUidValue()); icfResult.addArbitraryObjectAsParam("options", options); icfResult.addContext("connector", connIdConnectorFacade.getClass()); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Fetching connector object ObjectClass={}, UID={}, options={}", new Object[] { icfObjectClass, uid, ConnIdUtil.dumpOptions(options) }); } ConnectorObject co = null; try { // Invoke the ConnId connector InternalMonitor.recordConnectorOperation("getObject"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_GET, objectClassDefinition, uid); co = connIdConnectorFacade.getObject(icfObjectClass, uid, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET, objectClassDefinition, uid); icfResult.recordSuccess(); } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET, objectClassDefinition, ex, uid); String desc = this.getHumanReadableName() + " while getting object identified by ConnId UID '" + uid.getUidValue() + "'"; Throwable midpointEx = processConnIdException(ex, desc, icfResult); icfResult.computeStatus("Add object failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { icfResult.muteError(); throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof ConfigurationException) { throw (ConfigurationException) midpointEx; } else if (midpointEx instanceof SecurityViolationException) { throw (SecurityViolationException) midpointEx; } else if (midpointEx instanceof ObjectNotFoundException) { LOGGER.trace("Got ObjectNotFoundException while looking for resource object ConnId UID: {}", uid); return null; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { // This should not happen. But some connectors are very strange. throw new SystemException( "ERROR: " + midpointEx.getClass().getName() + ": " + midpointEx.getMessage(), midpointEx); } else { throw new SystemException(midpointEx.getClass().getName() + ": " + midpointEx.getMessage(), midpointEx); } } return co; } private void convertToIcfAttrsToGet(ObjectClassComplexTypeDefinition objectClassDefinition, AttributesToReturn attributesToReturn, OperationOptionsBuilder optionsBuilder) throws SchemaException { if (attributesToReturn == null) { return; } Collection<? extends ResourceAttributeDefinition> attrs = attributesToReturn.getAttributesToReturn(); if (attributesToReturn.isReturnDefaultAttributes() && !attributesToReturn.isReturnPasswordExplicit() && (attrs == null || attrs.isEmpty())) { return; } List<String> icfAttrsToGet = new ArrayList<>(); if (attributesToReturn.isReturnDefaultAttributes()) { if (supportsReturnDefaultAttributes) { optionsBuilder.setReturnDefaultAttributes(true); } else { // Add all the attributes that are defined as "returned by default" by the schema for (ResourceAttributeDefinition attributeDef : objectClassDefinition.getAttributeDefinitions()) { if (attributeDef.isReturnedByDefault()) { String attrName = connIdNameMapper.convertAttributeNameToIcf(attributeDef); icfAttrsToGet.add(attrName); } } } } if (attributesToReturn.isReturnPasswordExplicit() || (attributesToReturn.isReturnDefaultAttributes() && passwordReturnedByDefault())) { icfAttrsToGet.add(OperationalAttributes.PASSWORD_NAME); } if (attributesToReturn.isReturnAdministrativeStatusExplicit() || (attributesToReturn.isReturnDefaultAttributes() && enabledReturnedByDefault())) { icfAttrsToGet.add(OperationalAttributes.ENABLE_NAME); } if (attributesToReturn.isReturnLockoutStatusExplicit() || (attributesToReturn.isReturnDefaultAttributes() && lockoutReturnedByDefault())) { icfAttrsToGet.add(OperationalAttributes.LOCK_OUT_NAME); } if (attributesToReturn.isReturnValidFromExplicit() || (attributesToReturn.isReturnDefaultAttributes() && validFromReturnedByDefault())) { icfAttrsToGet.add(OperationalAttributes.ENABLE_DATE_NAME); } if (attributesToReturn.isReturnValidToExplicit() || (attributesToReturn.isReturnDefaultAttributes() && validToReturnedByDefault())) { icfAttrsToGet.add(OperationalAttributes.DISABLE_DATE_NAME); } if (attrs != null) { for (ResourceAttributeDefinition attrDef : attrs) { String attrName = connIdNameMapper.convertAttributeNameToIcf(attrDef); if (!icfAttrsToGet.contains(attrName)) { icfAttrsToGet.add(attrName); } } } // Log full list here. ConnId is shortening it and it cannot be seen in logs. LOGGER.trace("Converted attributes ConnId attibutesToGet: {}", icfAttrsToGet); optionsBuilder.setAttributesToGet(icfAttrsToGet); } private boolean passwordReturnedByDefault() { CredentialsCapabilityType capability = CapabilityUtil.getCapability(capabilities, CredentialsCapabilityType.class); return CapabilityUtil.isPasswordReturnedByDefault(capability); } private boolean enabledReturnedByDefault() { ActivationCapabilityType capability = CapabilityUtil.getCapability(capabilities, ActivationCapabilityType.class); return CapabilityUtil.isActivationStatusReturnedByDefault(capability); } private boolean lockoutReturnedByDefault() { ActivationCapabilityType capability = CapabilityUtil.getCapability(capabilities, ActivationCapabilityType.class); return CapabilityUtil.isActivationLockoutStatusReturnedByDefault(capability); } private boolean validFromReturnedByDefault() { ActivationCapabilityType capability = CapabilityUtil.getCapability(capabilities, ActivationCapabilityType.class); return CapabilityUtil.isActivationValidFromReturnedByDefault(capability); } private boolean validToReturnedByDefault() { ActivationCapabilityType capability = CapabilityUtil.getCapability(capabilities, ActivationCapabilityType.class); return CapabilityUtil.isActivationValidToReturnedByDefault(capability); } @Override public AsynchronousOperationReturnValue<Collection<ResourceAttribute<?>>> addObject( PrismObject<? extends ShadowType> shadow, Collection<Operation> additionalOperations, StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException, ObjectAlreadyExistsException, ConfigurationException { validateShadow(shadow, "add", false); ShadowType shadowType = shadow.asObjectable(); ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(shadow); OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".addObject"); result.addParam("resourceObject", shadow); result.addParam("additionalOperations", DebugUtil.debugDump(additionalOperations)); // because of serialization issues ObjectClassComplexTypeDefinition ocDef; ResourceAttributeContainerDefinition attrContDef = attributesContainer.getDefinition(); if (attrContDef != null) { ocDef = attrContDef.getComplexTypeDefinition(); } else { ocDef = resourceSchema.findObjectClassDefinition(shadow.asObjectable().getObjectClass()); if (ocDef == null) { throw new SchemaException("Unknown object class " + shadow.asObjectable().getObjectClass()); } } // getting icf object class from resource object class ObjectClass icfObjectClass = connIdNameMapper.objectClassToIcf(shadow, getSchemaNamespace(), connectorType, BooleanUtils.isNotFalse(legacySchema)); if (icfObjectClass == null) { result.recordFatalError("Couldn't get icf object class from " + shadow); throw new IllegalArgumentException("Couldn't get icf object class from " + shadow); } // setting ifc attributes from resource object attributes Set<Attribute> attributes = null; try { if (LOGGER.isTraceEnabled()) { LOGGER.trace("midPoint object before conversion:\n{}", attributesContainer.debugDump()); } attributes = connIdConvertor.convertFromResourceObject(attributesContainer, ocDef); if (shadowType.getCredentials() != null && shadowType.getCredentials().getPassword() != null) { PasswordType password = shadowType.getCredentials().getPassword(); ProtectedStringType protectedString = password.getValue(); GuardedString guardedPassword = ConnIdUtil.toGuardedString(protectedString, "new password", protector); if (guardedPassword != null) { attributes.add(AttributeBuilder.build(OperationalAttributes.PASSWORD_NAME, guardedPassword)); } } if (ActivationUtil.hasAdministrativeActivation(shadowType)) { attributes.add(AttributeBuilder.build(OperationalAttributes.ENABLE_NAME, ActivationUtil.isAdministrativeEnabled(shadowType))); } if (ActivationUtil.hasValidFrom(shadowType)) { attributes.add(AttributeBuilder.build(OperationalAttributes.ENABLE_DATE_NAME, XmlTypeConverter.toMillis(shadowType.getActivation().getValidFrom()))); } if (ActivationUtil.hasValidTo(shadowType)) { attributes.add(AttributeBuilder.build(OperationalAttributes.DISABLE_DATE_NAME, XmlTypeConverter.toMillis(shadowType.getActivation().getValidTo()))); } if (ActivationUtil.hasLockoutStatus(shadowType)) { attributes.add(AttributeBuilder.build(OperationalAttributes.LOCK_OUT_NAME, ActivationUtil.isLockedOut(shadowType))); } if (LOGGER.isTraceEnabled()) { LOGGER.trace("ConnId attributes after conversion:\n{}", ConnIdUtil.dump(attributes)); } } catch (SchemaException | RuntimeException ex) { result.recordFatalError("Error while converting resource object attributes. Reason: " + ex.getMessage(), ex); throw new SchemaException( "Error while converting resource object attributes. Reason: " + ex.getMessage(), ex); } if (attributes == null) { result.recordFatalError("Couldn't set attributes for icf."); throw new IllegalStateException("Couldn't set attributes for icf."); } List<String> icfAuxiliaryObjectClasses = new ArrayList<>(); for (QName auxiliaryObjectClass : shadowType.getAuxiliaryObjectClass()) { icfAuxiliaryObjectClasses.add(connIdNameMapper .objectClassToIcf(auxiliaryObjectClass, resourceSchemaNamespace, connectorType, false) .getObjectClassValue()); } if (!icfAuxiliaryObjectClasses.isEmpty()) { AttributeBuilder ab = new AttributeBuilder(); ab.setName(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME); ab.addValue(icfAuxiliaryObjectClasses); attributes.add(ab.build()); } OperationOptionsBuilder operationOptionsBuilder = new OperationOptionsBuilder(); OperationOptions options = operationOptionsBuilder.build(); checkAndExecuteAdditionalOperations(reporter, additionalOperations, BeforeAfterType.BEFORE, result); OperationResult connIdResult = result.createSubresult(ConnectorFacade.class.getName() + ".create"); connIdResult.addArbitraryObjectAsParam("objectClass", icfObjectClass); connIdResult.addArbitraryObjectCollectionAsParam("auxiliaryObjectClasses", icfAuxiliaryObjectClasses); connIdResult.addArbitraryObjectCollectionAsParam("attributes", attributes); connIdResult.addArbitraryObjectAsParam("options", options); connIdResult.addContext("connector", connIdConnectorFacade.getClass()); Uid uid = null; try { // CALL THE ConnId FRAMEWORK InternalMonitor.recordConnectorOperation("create"); InternalMonitor.recordConnectorModification("create"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_CREATE, ocDef, null); // TODO provide object name uid = connIdConnectorFacade.create(icfObjectClass, attributes, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_CREATE, ocDef, uid); } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_CREATE, ocDef, ex, null); // TODO name Throwable midpointEx = processConnIdException(ex, this, connIdResult); result.computeStatus("Add object failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof ObjectAlreadyExistsException) { throw (ObjectAlreadyExistsException) midpointEx; } else if (midpointEx instanceof CommunicationException) { // icfResult.muteError(); // result.muteError(); throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof ConfigurationException) { throw (ConfigurationException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } if (uid == null || uid.getUidValue() == null || uid.getUidValue().isEmpty()) { connIdResult.recordFatalError("ConnId did not returned UID after create"); result.computeStatus("Add object failed"); throw new GenericFrameworkException("ConnId did not returned UID after create"); } Collection<ResourceAttribute<?>> identifiers = ConnIdUtil.convertToIdentifiers(uid, attributesContainer.getDefinition().getComplexTypeDefinition(), resourceSchema); for (ResourceAttribute<?> identifier : identifiers) { attributesContainer.getValue().addReplaceExisting(identifier); } connIdResult.recordSuccess(); checkAndExecuteAdditionalOperations(reporter, additionalOperations, BeforeAfterType.AFTER, result); result.computeStatus(); return AsynchronousOperationReturnValue.wrap(attributesContainer.getAttributes(), result); } private void validateShadow(PrismObject<? extends ShadowType> shadow, String operation, boolean requireUid) { if (shadow == null) { throw new IllegalArgumentException("Cannot " + operation + " null " + shadow); } PrismContainer<?> attributesContainer = shadow.findContainer(ShadowType.F_ATTRIBUTES); if (attributesContainer == null) { throw new IllegalArgumentException("Cannot " + operation + " shadow without attributes container"); } ResourceAttributeContainer resourceAttributesContainer = ShadowUtil.getAttributesContainer(shadow); if (resourceAttributesContainer == null) { throw new IllegalArgumentException("Cannot " + operation + " shadow without attributes container of type ResourceAttributeContainer, got " + attributesContainer.getClass()); } if (requireUid) { Collection<ResourceAttribute<?>> identifiers = resourceAttributesContainer.getPrimaryIdentifiers(); if (identifiers == null || identifiers.isEmpty()) { throw new IllegalArgumentException("Cannot " + operation + " shadow without identifiers"); } } } // TODO [med] beware, this method does not obey its contract specified in the interface // (1) currently it does not return all the changes, only the 'side effect' changes // (2) it throws exceptions even if some of the changes were made // (3) among identifiers, only the UID value is updated on object rename // (other identifiers are ignored on input and output of this method) @Override public AsynchronousOperationReturnValue<Collection<PropertyModificationOperation>> modifyObject( ObjectClassComplexTypeDefinition objectClassDef, PrismObject<ShadowType> shadow, Collection<? extends ResourceAttribute<?>> identifiers, Collection<Operation> changes, StateReporter reporter, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, SchemaException, SecurityViolationException, ObjectAlreadyExistsException { OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".modifyObject"); result.addArbitraryObjectAsParam("objectClass", objectClassDef); result.addArbitraryObjectCollectionAsParam("identifiers", identifiers); result.addArbitraryObjectCollectionAsParam("changes", changes); if (changes.isEmpty()) { LOGGER.info("No modifications for connector object specified. Skipping processing."); result.recordNotApplicableIfUnknown(); return AsynchronousOperationReturnValue.wrap(new ArrayList<PropertyModificationOperation>(0), result); } ObjectClass objClass = connIdNameMapper.objectClassToIcf(objectClassDef, getSchemaNamespace(), connectorType, legacySchema); Uid uid; try { uid = getUid(objectClassDef, identifiers); } catch (SchemaException e) { result.recordFatalError(e); throw e; } if (uid == null) { result.recordFatalError("Cannot detemine UID from identifiers: " + identifiers); throw new IllegalArgumentException("Cannot detemine UID from identifiers: " + identifiers); } String originalUid = uid.getUidValue(); Set<Attribute> attributesToAdd = new HashSet<>(); Set<Attribute> attributesToUpdate = new HashSet<>(); Set<Attribute> attributesToRemove = new HashSet<>(); Set<Operation> additionalOperations = new HashSet<>(); PasswordChangeOperation passwordChangeOperation = null; Collection<PropertyDelta<?>> activationDeltas = new HashSet<>(); PropertyDelta<ProtectedStringType> passwordDelta = null; PropertyDelta<QName> auxiliaryObjectClassDelta = null; for (Operation operation : changes) { if (operation == null) { IllegalArgumentException e = new IllegalArgumentException("Null operation in modifyObject"); result.recordFatalError(e); throw e; } if (operation instanceof PropertyModificationOperation) { PropertyDelta<?> delta = ((PropertyModificationOperation) operation).getPropertyDelta(); if (delta.getPath().equivalent(new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS))) { auxiliaryObjectClassDelta = (PropertyDelta<QName>) delta; } } } try { ObjectClassComplexTypeDefinition structuralObjectClassDefinition = resourceSchema .findObjectClassDefinition(objectClassDef.getTypeName()); if (structuralObjectClassDefinition == null) { throw new SchemaException("No definition of structural object class " + objectClassDef.getTypeName() + " in " + description); } Map<QName, ObjectClassComplexTypeDefinition> auxiliaryObjectClassMap = new HashMap<>(); if (auxiliaryObjectClassDelta != null) { // Activation change means modification of attributes if (auxiliaryObjectClassDelta.isReplace()) { if (auxiliaryObjectClassDelta.getValuesToReplace() == null || auxiliaryObjectClassDelta.getValuesToReplace().isEmpty()) { attributesToUpdate .add(AttributeBuilder.build(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME)); } else { addConvertedValues(auxiliaryObjectClassDelta.getValuesToReplace(), attributesToUpdate, auxiliaryObjectClassMap); } } else { addConvertedValues(auxiliaryObjectClassDelta.getValuesToAdd(), attributesToAdd, auxiliaryObjectClassMap); addConvertedValues(auxiliaryObjectClassDelta.getValuesToDelete(), attributesToRemove, auxiliaryObjectClassMap); } } for (Operation operation : changes) { if (operation instanceof PropertyModificationOperation) { PropertyModificationOperation change = (PropertyModificationOperation) operation; PropertyDelta<?> delta = change.getPropertyDelta(); if (delta.getParentPath().equivalent(new ItemPath(ShadowType.F_ATTRIBUTES))) { if (delta.getDefinition() == null || !(delta.getDefinition() instanceof ResourceAttributeDefinition)) { ResourceAttributeDefinition def = objectClassDef .findAttributeDefinition(delta.getElementName()); if (def == null) { String message = "No definition for attribute " + delta.getElementName() + " used in modification delta"; result.recordFatalError(message); throw new SchemaException(message); } try { delta.applyDefinition(def); } catch (SchemaException e) { result.recordFatalError(e.getMessage(), e); throw e; } } boolean isInRemovedAuxClass = false; boolean isInAddedAuxClass = false; ResourceAttributeDefinition<Object> structAttrDef = structuralObjectClassDefinition .findAttributeDefinition(delta.getElementName()); // if this attribute is also in the structural object class. It does not matter if it is in // aux object class, we cannot add/remove it with the object class unless it is normally requested if (structAttrDef == null) { if (auxiliaryObjectClassDelta != null && auxiliaryObjectClassDelta.isDelete()) { // We need to change all the deltas of all the attributes that belong // to the removed auxiliary object class from REPLACE to DELETE. The change of // auxiliary object class and the change of the attributes must be done in // one operation. Otherwise we get schema error. And as auxiliary object class // is removed, the attributes must be removed as well. for (PrismPropertyValue<QName> auxPval : auxiliaryObjectClassDelta .getValuesToDelete()) { ObjectClassComplexTypeDefinition auxDef = auxiliaryObjectClassMap .get(auxPval.getValue()); ResourceAttributeDefinition<Object> attrDef = auxDef .findAttributeDefinition(delta.getElementName()); if (attrDef != null) { isInRemovedAuxClass = true; break; } } } if (auxiliaryObjectClassDelta != null && auxiliaryObjectClassDelta.isAdd()) { // We need to change all the deltas of all the attributes that belong // to the new auxiliary object class from REPLACE to ADD. The change of // auxiliary object class and the change of the attributes must be done in // one operation. Otherwise we get schema error. And as auxiliary object class // is added, the attributes must be added as well. for (PrismPropertyValue<QName> auxPval : auxiliaryObjectClassDelta .getValuesToAdd()) { ObjectClassComplexTypeDefinition auxOcDef = auxiliaryObjectClassMap .get(auxPval.getValue()); ResourceAttributeDefinition<Object> auxAttrDef = auxOcDef .findAttributeDefinition(delta.getElementName()); if (auxAttrDef != null) { isInAddedAuxClass = true; break; } } } } // Change in (ordinary) attributes. Transform to the ConnId attributes. if (delta.isAdd()) { ResourceAttribute<?> mpAttr = (ResourceAttribute<?>) delta.instantiateEmptyProperty(); mpAttr.addValues((Collection) PrismValue.cloneCollection(delta.getValuesToAdd())); Attribute connIdAttr = connIdConvertor.convertToConnIdAttribute(mpAttr, objectClassDef); if (mpAttr.getDefinition().isMultiValue()) { attributesToAdd.add(connIdAttr); } else { // Force "update" for single-valued attributes instead of "add". This is saving one // read in some cases. It should also make no substantial difference in such case. // But it is working around some connector bugs. attributesToUpdate.add(connIdAttr); } } if (delta.isDelete()) { ResourceAttribute<?> mpAttr = (ResourceAttribute<?>) delta.instantiateEmptyProperty(); if (mpAttr.getDefinition().isMultiValue() || isInRemovedAuxClass) { mpAttr.addValues( (Collection) PrismValue.cloneCollection(delta.getValuesToDelete())); Attribute connIdAttr = connIdConvertor.convertToConnIdAttribute(mpAttr, objectClassDef); attributesToRemove.add(connIdAttr); } else { // Force "update" for single-valued attributes instead of "add". This is saving one // read in some cases. // Update attribute to no values. This will efficiently clean up the attribute. // It should also make no substantial difference in such case. // But it is working around some connector bugs. Attribute connIdAttr = connIdConvertor.convertToConnIdAttribute(mpAttr, objectClassDef); // update with EMTPY value. The mpAttr.addValues() is NOT in this branch attributesToUpdate.add(connIdAttr); } } if (delta.isReplace()) { ResourceAttribute<?> mpAttr = (ResourceAttribute<?>) delta.instantiateEmptyProperty(); mpAttr.addValues((Collection) PrismValue.cloneCollection(delta.getValuesToReplace())); Attribute connIdAttr = connIdConvertor.convertToConnIdAttribute(mpAttr, objectClassDef); if (isInAddedAuxClass) { attributesToAdd.add(connIdAttr); } else { attributesToUpdate.add(connIdAttr); } } } else if (delta.getParentPath().equivalent(new ItemPath(ShadowType.F_ACTIVATION))) { activationDeltas.add(delta); } else if (delta.getParentPath().equivalent( new ItemPath(new ItemPath(ShadowType.F_CREDENTIALS), CredentialsType.F_PASSWORD))) { passwordDelta = (PropertyDelta<ProtectedStringType>) delta; } else if (delta.getPath().equivalent(new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS))) { // already processed } else { throw new SchemaException("Change of unknown attribute " + delta.getPath()); } } else if (operation instanceof PasswordChangeOperation) { passwordChangeOperation = (PasswordChangeOperation) operation; // TODO: check for multiple occurrences and fail } else if (operation instanceof ExecuteProvisioningScriptOperation) { ExecuteProvisioningScriptOperation scriptOperation = (ExecuteProvisioningScriptOperation) operation; additionalOperations.add(scriptOperation); } else { throw new IllegalArgumentException( "Unknown operation type " + operation.getClass().getName() + ": " + operation); } } } catch (SchemaException | RuntimeException e) { result.recordFatalError(e); throw e; } if (LOGGER.isTraceEnabled()) { LOGGER.trace("attributes:\nADD: {}\nUPDATE: {}\nREMOVE: {}", attributesToAdd, attributesToUpdate, attributesToRemove); } // Needs three complete try-catch blocks because we need to create // icfResult for each operation // and handle the faults individually checkAndExecuteAdditionalOperations(reporter, additionalOperations, BeforeAfterType.BEFORE, result); OperationResult connIdResult = null; try { if (!attributesToAdd.isEmpty()) { OperationOptions options = new OperationOptionsBuilder().build(); connIdResult = result.createSubresult(ConnectorFacade.class.getName() + ".addAttributeValues"); connIdResult.addArbitraryObjectAsParam("objectClass", objectClassDef); connIdResult.addParam("uid", uid.getUidValue()); connIdResult.addArbitraryObjectAsParam("attributes", attributesToAdd); connIdResult.addArbitraryObjectAsParam("options", options); connIdResult.addContext("connector", connIdConnectorFacade.getClass()); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Invoking ConnId addAttributeValues(), objectclass={}, uid={}, attributes: {}", new Object[] { objClass, uid, dumpAttributes(attributesToAdd) }); } InternalMonitor.recordConnectorOperation("addAttributeValues"); InternalMonitor.recordConnectorModification("addAttributeValues"); // Invoking ConnId recordIcfOperationStart(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, uid); uid = connIdConnectorFacade.addAttributeValues(objClass, uid, attributesToAdd, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, null, uid); connIdResult.recordSuccess(); } } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, ex, uid); String desc = this.getHumanReadableName() + " while adding attribute values to object identified by ConnId UID '" + uid.getUidValue() + "'"; Throwable midpointEx = processConnIdException(ex, desc, connIdResult); result.computeStatus("Adding attribute values failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof ObjectNotFoundException) { throw (ObjectNotFoundException) midpointEx; } else if (midpointEx instanceof CommunicationException) { //in this situation this is not a critical error, becasue we know to handle it..so mute the error and sign it as expected result.muteError(); connIdResult.muteError(); throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof AlreadyExistsException) { throw (AlreadyExistsException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof SecurityViolationException) { throw (SecurityViolationException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } if (!attributesToUpdate.isEmpty() || activationDeltas != null || passwordDelta != null || auxiliaryObjectClassDelta != null) { try { if (activationDeltas != null) { // Activation change means modification of attributes convertFromActivation(attributesToUpdate, activationDeltas); } if (passwordDelta != null) { // Activation change means modification of attributes convertFromPassword(attributesToUpdate, passwordDelta); } } catch (SchemaException ex) { result.recordFatalError( "Error while converting resource object attributes. Reason: " + ex.getMessage(), ex); throw new SchemaException( "Error while converting resource object attributes. Reason: " + ex.getMessage(), ex); } catch (RuntimeException ex) { result.recordFatalError( "Error while converting resource object attributes. Reason: " + ex.getMessage(), ex); throw ex; } if (!attributesToUpdate.isEmpty()) { OperationOptions options = new OperationOptionsBuilder().build(); connIdResult = result.createSubresult(ConnectorFacade.class.getName() + ".update"); connIdResult.addArbitraryObjectAsParam("objectClass", objectClassDef); connIdResult.addParam("uid", uid == null ? "null" : uid.getUidValue()); connIdResult.addArbitraryObjectAsParam("attributes", attributesToUpdate); connIdResult.addArbitraryObjectAsParam("options", options); connIdResult.addContext("connector", connIdConnectorFacade.getClass()); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Invoking ConnId update(), objectclass={}, uid={}, attributes: {}", new Object[] { objClass, uid, dumpAttributes(attributesToUpdate) }); } try { // Call ConnId InternalMonitor.recordConnectorOperation("update"); InternalMonitor.recordConnectorModification("update"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, uid); uid = connIdConnectorFacade.update(objClass, uid, attributesToUpdate, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, null, uid); connIdResult.recordSuccess(); } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, ex, uid); String desc = this.getHumanReadableName() + " while updating object identified by ConnId UID '" + uid.getUidValue() + "'"; Throwable midpointEx = processConnIdException(ex, desc, connIdResult); result.computeStatus("Update failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof ObjectNotFoundException) { throw (ObjectNotFoundException) midpointEx; } else if (midpointEx instanceof CommunicationException) { //in this situation this is not a critical error, becasue we know to handle it..so mute the error and sign it as expected result.muteError(); connIdResult.muteError(); throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof ObjectAlreadyExistsException) { throw (ObjectAlreadyExistsException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof SecurityViolationException) { throw (SecurityViolationException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } } } try { if (!attributesToRemove.isEmpty()) { OperationOptions options = new OperationOptionsBuilder().build(); connIdResult = result.createSubresult(ConnectorFacade.class.getName() + ".removeAttributeValues"); connIdResult.addArbitraryObjectAsParam("objectClass", objectClassDef); connIdResult.addParam("uid", uid.getUidValue()); connIdResult.addArbitraryObjectAsParam("attributes", attributesToRemove); connIdResult.addArbitraryObjectAsParam("options", options); connIdResult.addContext("connector", connIdConnectorFacade.getClass()); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Invoking ConnId removeAttributeValues(), objectclass={}, uid={}, attributes: {}", new Object[] { objClass, uid, dumpAttributes(attributesToRemove) }); } InternalMonitor.recordConnectorOperation("removeAttributeValues"); InternalMonitor.recordConnectorModification("removeAttributeValues"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, uid); uid = connIdConnectorFacade.removeAttributeValues(objClass, uid, attributesToRemove, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, null, uid); connIdResult.recordSuccess(); } } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_UPDATE, objectClassDef, ex, uid); String desc = this.getHumanReadableName() + " while removing attribute values from object identified by ConnId UID '" + uid.getUidValue() + "'"; Throwable midpointEx = processConnIdException(ex, desc, connIdResult); result.computeStatus("Removing attribute values failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof ObjectNotFoundException) { throw (ObjectNotFoundException) midpointEx; } else if (midpointEx instanceof CommunicationException) { //in this situation this is not a critical error, becasue we know to handle it..so mute the error and sign it as expected result.muteError(); connIdResult.muteError(); throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof ObjectAlreadyExistsException) { throw (ObjectAlreadyExistsException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof SecurityViolationException) { throw (SecurityViolationException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } checkAndExecuteAdditionalOperations(reporter, additionalOperations, BeforeAfterType.AFTER, result); result.computeStatus(); Collection<PropertyModificationOperation> sideEffectChanges = new ArrayList<>(); if (!originalUid.equals(uid.getUidValue())) { // UID was changed during the operation, this is most likely a // rename PropertyDelta<String> uidDelta = createUidDelta(uid, getUidDefinition(objectClassDef, identifiers)); PropertyModificationOperation uidMod = new PropertyModificationOperation(uidDelta); // TODO what about matchingRuleQName ? sideEffectChanges.add(uidMod); replaceUidValue(objectClassDef, identifiers, uid); } return AsynchronousOperationReturnValue.wrap(sideEffectChanges, result); } private PropertyDelta<String> createUidDelta(Uid uid, ResourceAttributeDefinition uidDefinition) { PropertyDelta<String> uidDelta = new PropertyDelta<String>( new ItemPath(ShadowType.F_ATTRIBUTES, uidDefinition.getName()), uidDefinition, prismContext); uidDelta.setValueToReplace(new PrismPropertyValue<>(uid.getUidValue())); return uidDelta; } private String dumpAttributes(Set<Attribute> attributes) { if (attributes == null) { return "(null)"; } if (attributes.isEmpty()) { return "(empty)"; } StringBuilder sb = new StringBuilder(); for (Attribute attr : attributes) { sb.append("\n"); if (attr.getValue() == null || attr.getValue().isEmpty()) { sb.append(attr.getName()); sb.append(" (empty)"); } else { for (Object value : attr.getValue()) { sb.append(attr.getName()); sb.append(" = "); sb.append(value); } } } return sb.toString(); } @Override public AsynchronousOperationResult deleteObject(ObjectClassComplexTypeDefinition objectClass, Collection<Operation> additionalOperations, PrismObject<ShadowType> shadow, Collection<? extends ResourceAttribute<?>> identifiers, StateReporter reporter, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, SchemaException { Validate.notNull(objectClass, "No objectclass"); OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".deleteObject"); result.addArbitraryObjectCollectionAsParam("identifiers", identifiers); ObjectClass objClass = connIdNameMapper.objectClassToIcf(objectClass, getSchemaNamespace(), connectorType, legacySchema); Uid uid; try { uid = getUid(objectClass, identifiers); } catch (SchemaException e) { result.recordFatalError(e); throw e; } checkAndExecuteAdditionalOperations(reporter, additionalOperations, BeforeAfterType.BEFORE, result); OperationResult icfResult = result.createSubresult(ConnectorFacade.class.getName() + ".delete"); icfResult.addArbitraryObjectAsParam("uid", uid); icfResult.addArbitraryObjectAsParam("objectClass", objClass); icfResult.addContext("connector", connIdConnectorFacade.getClass()); try { InternalMonitor.recordConnectorOperation("delete"); InternalMonitor.recordConnectorModification("delete"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_DELETE, objectClass, uid); connIdConnectorFacade.delete(objClass, uid, new OperationOptionsBuilder().build()); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_DELETE, objectClass, null, uid); icfResult.recordSuccess(); } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_DELETE, objectClass, ex, uid); String desc = this.getHumanReadableName() + " while deleting object identified by ConnId UID '" + uid.getUidValue() + "'"; Throwable midpointEx = processConnIdException(ex, desc, icfResult); result.computeStatus("Removing attribute values failed"); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof ObjectNotFoundException) { throw (ObjectNotFoundException) midpointEx; } else if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { // Schema exception during delete? It must be a missing UID throw new IllegalArgumentException(midpointEx.getMessage(), midpointEx); } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } checkAndExecuteAdditionalOperations(reporter, additionalOperations, BeforeAfterType.AFTER, result); result.computeStatus(); return AsynchronousOperationResult.wrap(result); } @Override public PrismProperty<?> deserializeToken(Object serializedToken) { return createTokenProperty(serializedToken); } @Override public <T> PrismProperty<T> fetchCurrentToken(ObjectClassComplexTypeDefinition objectClassDef, StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException { OperationResult result = parentResult .createSubresult(ConnectorInstance.class.getName() + ".fetchCurrentToken"); result.addArbitraryObjectAsParam("objectClass", objectClassDef); ObjectClass icfObjectClass; if (objectClassDef == null) { icfObjectClass = ObjectClass.ALL; } else { icfObjectClass = connIdNameMapper.objectClassToIcf(objectClassDef, getSchemaNamespace(), connectorType, legacySchema); } OperationResult icfResult = result.createSubresult(ConnectorFacade.class.getName() + ".sync"); icfResult.addContext("connector", connIdConnectorFacade.getClass()); icfResult.addArbitraryObjectAsParam("icfObjectClass", icfObjectClass); SyncToken syncToken = null; try { InternalMonitor.recordConnectorOperation("getLatestSyncToken"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_GET_LATEST_SYNC_TOKEN, objectClassDef); syncToken = connIdConnectorFacade.getLatestSyncToken(icfObjectClass); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET_LATEST_SYNC_TOKEN, objectClassDef); icfResult.recordSuccess(); icfResult.addReturn("syncToken", syncToken == null ? null : String.valueOf(syncToken.getValue())); } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_GET_LATEST_SYNC_TOKEN, objectClassDef, ex); Throwable midpointEx = processConnIdException(ex, this, icfResult); result.computeStatus(); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } if (syncToken == null) { result.recordWarning("Resource have not provided a current sync token"); return null; } PrismProperty<T> property = getToken(syncToken); result.recordSuccess(); return property; } @Override public List<Change> fetchChanges(ObjectClassComplexTypeDefinition objectClass, PrismProperty<?> lastToken, AttributesToReturn attrsToReturn, StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException { OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".fetchChanges"); result.addArbitraryObjectAsContext("objectClass", objectClass); result.addArbitraryObjectAsParam("lastToken", lastToken); // create sync token from the property last token SyncToken syncToken = null; try { syncToken = getSyncToken(lastToken); LOGGER.trace("Sync token created from the property last token: {}", syncToken == null ? null : syncToken.getValue()); } catch (SchemaException ex) { result.recordFatalError(ex.getMessage(), ex); throw new SchemaException(ex.getMessage(), ex); } final List<SyncDelta> syncDeltas = new ArrayList<>(); // get icf object class ObjectClass icfObjectClass; if (objectClass == null) { icfObjectClass = ObjectClass.ALL; } else { icfObjectClass = connIdNameMapper.objectClassToIcf(objectClass, getSchemaNamespace(), connectorType, legacySchema); } OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); if (objectClass != null) { convertToIcfAttrsToGet(objectClass, attrsToReturn, optionsBuilder); } OperationOptions options = optionsBuilder.build(); SyncResultsHandler syncHandler = new SyncResultsHandler() { @Override public boolean handle(SyncDelta delta) { LOGGER.trace("Detected sync delta: {}", delta); return syncDeltas.add(delta); } }; OperationResult connIdResult = result.createSubresult(ConnectorFacade.class.getName() + ".sync"); connIdResult.addContext("connector", connIdConnectorFacade.getClass()); connIdResult.addArbitraryObjectAsParam("connIdObjectClass", icfObjectClass); connIdResult.addArbitraryObjectAsParam("syncToken", syncToken); connIdResult.addArbitraryObjectAsParam("syncHandler", syncHandler); SyncToken lastReceivedToken; try { InternalMonitor.recordConnectorOperation("sync"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_SYNC, objectClass); lastReceivedToken = connIdConnectorFacade.sync(icfObjectClass, syncToken, syncHandler, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SYNC, objectClass); connIdResult.recordSuccess(); connIdResult.addReturn(OperationResult.RETURN_COUNT, syncDeltas.size()); } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SYNC, objectClass, ex); Throwable midpointEx = processConnIdException(ex, this, connIdResult); result.computeStatus(); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } // convert changes from icf to midpoint Change List<Change> changeList; try { changeList = getChangesFromSyncDeltas(icfObjectClass, syncDeltas, resourceSchema, result); } catch (SchemaException ex) { result.recordFatalError(ex.getMessage(), ex); throw new SchemaException(ex.getMessage(), ex); } if (lastReceivedToken != null) { Change lastChange = new Change((ObjectDelta) null, getToken(lastReceivedToken)); LOGGER.trace("Adding last change: {}", lastChange); changeList.add(lastChange); } result.recordSuccess(); result.addReturn(OperationResult.RETURN_COUNT, changeList == null ? 0 : changeList.size()); return changeList; } @Override public void test(OperationResult parentResult) { OperationResult connectionResult = parentResult .createSubresult(ConnectorTestOperation.CONNECTOR_CONNECTION.getOperation()); connectionResult.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, ConnectorInstanceConnIdImpl.class); connectionResult.addContext("connector", connectorType); try { InternalMonitor.recordConnectorOperation("test"); connIdConnectorFacade.test(); connectionResult.recordSuccess(); } catch (UnsupportedOperationException ex) { // Connector does not support test connection. connectionResult.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Operation not supported by the connector", ex); // Do not rethrow. Recording the status is just OK. } catch (Throwable icfEx) { Throwable midPointEx = processConnIdException(icfEx, this, connectionResult); connectionResult.recordFatalError(midPointEx); } } @Override public SearchResultMetadata search(final ObjectClassComplexTypeDefinition objectClassDefinition, final ObjectQuery query, final ShadowResultHandler handler, AttributesToReturn attributesToReturn, PagedSearchCapabilityType pagedSearchCapabilityType, SearchHierarchyConstraints searchHierarchyConstraints, final StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SecurityViolationException, SchemaException, ObjectNotFoundException { // Result type for this operation final OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".search"); result.addArbitraryObjectAsParam("objectClass", objectClassDefinition); result.addContext("connector", connectorType); if (objectClassDefinition == null) { result.recordFatalError("Object class not defined"); throw new IllegalArgumentException("objectClass not defined"); } ObjectClass icfObjectClass = connIdNameMapper.objectClassToIcf(objectClassDefinition, getSchemaNamespace(), connectorType, legacySchema); if (icfObjectClass == null) { IllegalArgumentException ex = new IllegalArgumentException( "Unable to determine object class from QName " + objectClassDefinition + " while attempting to search objects by " + ObjectTypeUtil.toShortString(connectorType)); result.recordFatalError("Unable to determine object class", ex); throw ex; } final PrismObjectDefinition<ShadowType> objectDefinition = toShadowDefinition(objectClassDefinition); if (pagedSearchCapabilityType == null) { pagedSearchCapabilityType = getCapability(PagedSearchCapabilityType.class); } final boolean useConnectorPaging = pagedSearchCapabilityType != null; if (!useConnectorPaging && query != null && query.getPaging() != null && (query.getPaging().getOffset() != null || query.getPaging().getMaxSize() != null)) { InternalMonitor.recordCount(InternalCounters.CONNECTOR_SIMULATED_PAGING_SEARCH_COUNT); } final Holder<Integer> countHolder = new Holder<>(0); ResultsHandler icfHandler = new ResultsHandler() { @Override public boolean handle(ConnectorObject connectorObject) { // Convert ConnId-specific connector object to a generic // ResourceObject recordIcfOperationSuspend(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition); int count = countHolder.getValue(); countHolder.setValue(count + 1); if (!useConnectorPaging) { // TODO allow offset or maxSize be null if (query != null && query.getPaging() != null && query.getPaging().getOffset() != null && query.getPaging().getMaxSize() != null) { if (count < query.getPaging().getOffset()) { recordResume(); return true; } if (count == (query.getPaging().getOffset() + query.getPaging().getMaxSize())) { recordResume(); return false; } } } PrismObject<ShadowType> resourceObject; Validate.notNull(connectorObject, "null connector object"); try { resourceObject = connIdConvertor.convertToResourceObject(connectorObject, objectDefinition, false, caseIgnoreAttributeNames, legacySchema); } catch (SchemaException e) { recordResume(); throw new IntermediateException(e); } Validate.notNull(resourceObject, "null resource object"); // .. and pass it to the handler boolean cont = handler.handle(resourceObject); if (!cont) { result.recordWarning("Stopped on request from the handler"); } recordResume(); return cont; } private void recordResume() { recordIcfOperationResume(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition); } }; OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); try { convertToIcfAttrsToGet(objectClassDefinition, attributesToReturn, optionsBuilder); if (query != null && query.isAllowPartialResults()) { optionsBuilder.setAllowPartialResults(query.isAllowPartialResults()); } // preparing paging-related options if (useConnectorPaging && query != null && query.getPaging() != null) { ObjectPaging paging = query.getPaging(); if (paging.getOffset() != null) { optionsBuilder.setPagedResultsOffset(paging.getOffset() + 1); // ConnId API says the numbering starts at 1 } if (paging.getMaxSize() != null) { optionsBuilder.setPageSize(paging.getMaxSize()); } QName orderByAttributeName; boolean isAscending; ItemPath orderByPath = paging.getOrderBy(); String desc; if (orderByPath != null && !orderByPath.isEmpty()) { orderByAttributeName = ShadowUtil.getAttributeName(orderByPath, "OrderBy path"); if (SchemaConstants.C_NAME.equals(orderByAttributeName)) { orderByAttributeName = SchemaConstants.ICFS_NAME; } isAscending = paging.getDirection() != OrderDirection.DESCENDING; desc = "(explicitly specified orderBy attribute)"; } else { orderByAttributeName = pagedSearchCapabilityType.getDefaultSortField(); isAscending = pagedSearchCapabilityType .getDefaultSortDirection() != OrderDirectionType.DESCENDING; desc = "(default orderBy attribute from capability definition)"; } if (orderByAttributeName != null) { String orderByIcfName = connIdNameMapper.convertAttributeNameToIcf(orderByAttributeName, objectClassDefinition, desc); optionsBuilder.setSortKeys(new SortKey(orderByIcfName, isAscending)); } } if (searchHierarchyConstraints != null) { ResourceObjectIdentification baseContextIdentification = searchHierarchyConstraints .getBaseContext(); // Only LDAP connector really supports base context. And this one will work better with // DN. And DN is secondary identifier (__NAME__). This is ugly, but practical. It works around ConnId problems. ResourceAttribute<?> secondaryIdentifier = baseContextIdentification.getSecondaryIdentifier(); if (secondaryIdentifier == null) { SchemaException e = new SchemaException( "No secondary identifier in base context identification " + baseContextIdentification); result.recordFatalError(e); throw e; } String secondaryIdentifierValue = secondaryIdentifier.getRealValue(String.class); ObjectClass baseContextIcfObjectClass = connIdNameMapper.objectClassToIcf( baseContextIdentification.getObjectClassDefinition(), getSchemaNamespace(), connectorType, legacySchema); QualifiedUid containerQualifiedUid = new QualifiedUid(baseContextIcfObjectClass, new Uid(secondaryIdentifierValue)); optionsBuilder.setContainer(containerQualifiedUid); } } catch (SchemaException e) { result.recordFatalError(e); throw e; } // Relax completeness requirements. This is a search, not get. So it is OK to // return incomplete member lists and similar attributes. optionsBuilder.setAllowPartialAttributeValues(true); OperationOptions options = optionsBuilder.build(); Filter filter; try { filter = convertFilterToIcf(query, objectClassDefinition); } catch (SchemaException | RuntimeException e) { result.recordFatalError(e); throw e; } // Connector operation cannot create result for itself, so we need to // create result for it OperationResult icfResult = result.createSubresult(ConnectorFacade.class.getName() + ".search"); icfResult.addArbitraryObjectAsParam("objectClass", icfObjectClass); icfResult.addContext("connector", connIdConnectorFacade.getClass()); SearchResult icfSearchResult; try { InternalMonitor.recordConnectorOperation("search"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition); icfSearchResult = connIdConnectorFacade.search(icfObjectClass, filter, icfHandler, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition); icfResult.recordSuccess(); } catch (IntermediateException inex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition, inex); SchemaException ex = (SchemaException) inex.getCause(); icfResult.recordFatalError(ex); result.recordFatalError(ex); throw ex; } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition, ex); Throwable midpointEx = processConnIdException(ex, this, icfResult); result.computeStatus(); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof ObjectNotFoundException) { throw (ObjectNotFoundException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof SecurityViolationException) { throw (SecurityViolationException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } SearchResultMetadata metadata = null; if (icfSearchResult != null) { metadata = new SearchResultMetadata(); metadata.setPagingCookie(icfSearchResult.getPagedResultsCookie()); if (icfSearchResult.getRemainingPagedResults() >= 0) { metadata.setApproxNumberOfAllResults(icfSearchResult.getRemainingPagedResults()); } if (!icfSearchResult.isAllResultsReturned()) { metadata.setPartialResults(true); } } if (result.isUnknown()) { result.recordSuccess(); } return metadata; } @Override public int count(ObjectClassComplexTypeDefinition objectClassDefinition, final ObjectQuery query, PagedSearchCapabilityType pagedSearchCapabilityType, StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException, UnsupportedOperationException { // Result type for this operation final OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".count"); result.addArbitraryObjectAsParam("objectClass", objectClassDefinition); result.addContext("connector", connectorType); if (objectClassDefinition == null) { result.recordFatalError("Object class not defined"); throw new IllegalArgumentException("objectClass not defined"); } ObjectClass icfObjectClass = connIdNameMapper.objectClassToIcf(objectClassDefinition, getSchemaNamespace(), connectorType, legacySchema); if (icfObjectClass == null) { IllegalArgumentException ex = new IllegalArgumentException( "Unable to determine object class from QName " + objectClassDefinition + " while attempting to search objects by " + ObjectTypeUtil.toShortString(connectorType)); result.recordFatalError("Unable to determine object class", ex); throw ex; } final boolean useConnectorPaging = pagedSearchCapabilityType != null; if (!useConnectorPaging) { throw new UnsupportedOperationException( "ConnectorInstanceIcfImpl.count operation is supported only in combination with connector-implemented paging"); } OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); optionsBuilder.setAttributesToGet(Name.NAME); optionsBuilder.setPagedResultsOffset(1); optionsBuilder.setPageSize(1); if (pagedSearchCapabilityType.getDefaultSortField() != null) { String orderByIcfName = connIdNameMapper.convertAttributeNameToIcf( pagedSearchCapabilityType.getDefaultSortField(), objectClassDefinition, "(default sorting field)"); boolean isAscending = pagedSearchCapabilityType .getDefaultSortDirection() != OrderDirectionType.DESCENDING; optionsBuilder.setSortKeys(new SortKey(orderByIcfName, isAscending)); } OperationOptions options = optionsBuilder.build(); // Connector operation cannot create result for itself, so we need to // create result for it OperationResult icfResult = result.createSubresult(ConnectorFacade.class.getName() + ".search"); icfResult.addArbitraryObjectAsParam("objectClass", icfObjectClass); icfResult.addContext("connector", connIdConnectorFacade.getClass()); int retval; try { Filter filter = convertFilterToIcf(query, objectClassDefinition); final Holder<Integer> fetched = new Holder<>(0); ResultsHandler icfHandler = new ResultsHandler() { @Override public boolean handle(ConnectorObject connectorObject) { fetched.setValue(fetched.getValue() + 1); // actually, this should execute at most once return false; } }; InternalMonitor.recordConnectorOperation("search"); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition); SearchResult searchResult = connIdConnectorFacade.search(icfObjectClass, filter, icfHandler, options); recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition); if (searchResult == null || searchResult.getRemainingPagedResults() == -1) { throw new UnsupportedOperationException( "Connector does not seem to support paged searches or does not provide object count information"); } else { retval = fetched.getValue() + searchResult.getRemainingPagedResults(); } icfResult.recordSuccess(); } catch (IntermediateException inex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition, inex); SchemaException ex = (SchemaException) inex.getCause(); icfResult.recordFatalError(ex); result.recordFatalError(ex); throw ex; } catch (UnsupportedOperationException uoe) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition, uoe); icfResult.recordFatalError(uoe); result.recordFatalError(uoe); throw uoe; } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SEARCH, objectClassDefinition, ex); Throwable midpointEx = processConnIdException(ex, this, icfResult); result.computeStatus(); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { throw (SchemaException) midpointEx; } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } if (result.isUnknown()) { result.recordSuccess(); } return retval; } private Filter convertFilterToIcf(ObjectQuery query, ObjectClassComplexTypeDefinition objectClassDefinition) throws SchemaException { Filter filter = null; if (query != null && query.getFilter() != null) { FilterInterpreter interpreter = new FilterInterpreter(objectClassDefinition); LOGGER.trace("Start to convert filter: {}", query.getFilter().debugDump()); filter = interpreter.interpret(query.getFilter(), connIdNameMapper); if (LOGGER.isTraceEnabled()) { LOGGER.trace("ConnId filter: {}", ConnIdUtil.dump(filter)); } } return filter; } // UTILITY METHODS private Uid getUid(ResourceObjectIdentification resourceObjectIdentification) throws SchemaException { ResourceAttribute<String> primaryIdentifier = resourceObjectIdentification.getPrimaryIdentifier(); if (primaryIdentifier == null) { return null; } String uidValue = primaryIdentifier.getRealValue(); String nameValue = null; Collection<? extends ResourceAttribute<?>> secondaryIdentifiers = resourceObjectIdentification .getSecondaryIdentifiers(); if (secondaryIdentifiers != null && secondaryIdentifiers.size() == 1) { nameValue = (String) secondaryIdentifiers.iterator().next().getRealValue(); } if (uidValue != null) { if (nameValue == null) { return new Uid(uidValue); } else { return new Uid(uidValue, new Name(nameValue)); } } return null; } /** * Looks up ConnId Uid identifier in a (potentially multi-valued) set of * identifiers. Handy method to convert midPoint identifier style to an ConnId * identifier style. * * @param identifiers * midPoint resource object identifiers * @return ConnId UID or null */ private Uid getUid(ObjectClassComplexTypeDefinition objectClass, Collection<? extends ResourceAttribute<?>> identifiers) throws SchemaException { if (identifiers.size() == 0) { return null; } if (identifiers.size() == 1) { try { return new Uid((String) identifiers.iterator().next().getRealValue()); } catch (IllegalArgumentException e) { throw new SchemaException(e.getMessage(), e); } } String uidValue = null; String nameValue = null; for (ResourceAttribute<?> attr : identifiers) { if (objectClass.isPrimaryIdentifier(attr.getElementName())) { uidValue = ((ResourceAttribute<String>) attr).getValue().getValue(); } if (objectClass.isSecondaryIdentifier(attr.getElementName())) { ResourceAttributeDefinition<?> attrDef = objectClass.findAttributeDefinition(attr.getElementName()); String frameworkAttributeName = attrDef.getFrameworkAttributeName(); if (Name.NAME.equals(frameworkAttributeName)) { nameValue = ((ResourceAttribute<String>) attr).getValue().getValue(); } } } if (uidValue != null) { if (nameValue == null) { return new Uid(uidValue); } else { return new Uid(uidValue, new Name(nameValue)); } } // fallback, compatibility for (ResourceAttribute<?> attr : identifiers) { if (attr.getElementName().equals(SchemaConstants.ICFS_UID)) { return new Uid(((ResourceAttribute<String>) attr).getValue().getValue()); } } return null; } private void replaceUidValue(ObjectClassComplexTypeDefinition objectClass, Collection<? extends ResourceAttribute<?>> identifiers, Uid newUid) { if (identifiers.size() == 0) { throw new IllegalStateException("No identifiers"); } if (identifiers.size() == 1) { identifiers.iterator().next().setValue(new PrismPropertyValue(newUid.getUidValue())); return; } for (ResourceAttribute<?> attr : identifiers) { if (objectClass.isPrimaryIdentifier(attr.getElementName())) { ((ResourceAttribute<String>) attr).setValue(new PrismPropertyValue(newUid.getUidValue())); return; } } // fallback, compatibility for (ResourceAttribute<?> attr : identifiers) { if (attr.getElementName().equals(SchemaConstants.ICFS_UID)) { attr.setValue(new PrismPropertyValue(newUid.getUidValue())); // expecting the UID property is of type String return; } } throw new IllegalStateException("No UID attribute in " + identifiers); } private ResourceAttributeDefinition getUidDefinition(ObjectClassComplexTypeDefinition objectClass, Collection<? extends ResourceAttribute<?>> identifiers) { if (identifiers.size() == 0) { return null; } if (identifiers.size() == 1) { return identifiers.iterator().next().getDefinition(); } for (ResourceAttribute<?> attr : identifiers) { if (objectClass.isPrimaryIdentifier(attr.getElementName())) { return ((ResourceAttribute<String>) attr).getDefinition(); } } // fallback, compatibility for (ResourceAttribute<?> attr : identifiers) { if (attr.getElementName().equals(SchemaConstants.ICFS_UID)) { return attr.getDefinition(); } } return null; } private void convertFromActivation(Set<Attribute> updateAttributes, Collection<PropertyDelta<?>> activationDeltas) throws SchemaException { for (PropertyDelta<?> propDelta : activationDeltas) { if (propDelta.getElementName().equals(ActivationType.F_ADMINISTRATIVE_STATUS)) { ActivationStatusType status = getPropertyNewValue(propDelta, ActivationStatusType.class); if (status == null) { updateAttributes.add(AttributeBuilder.build(OperationalAttributes.ENABLE_NAME)); } else { updateAttributes.add(AttributeBuilder.build(OperationalAttributes.ENABLE_NAME, status == ActivationStatusType.ENABLED)); } } else if (propDelta.getElementName().equals(ActivationType.F_VALID_FROM)) { XMLGregorianCalendar xmlCal = getPropertyNewValue(propDelta, XMLGregorianCalendar.class);//propDelta.getPropertyNew().getValue(XMLGregorianCalendar.class).getValue(); updateAttributes.add(AttributeBuilder.build(OperationalAttributes.ENABLE_DATE_NAME, xmlCal != null ? XmlTypeConverter.toMillis(xmlCal) : null)); } else if (propDelta.getElementName().equals(ActivationType.F_VALID_TO)) { XMLGregorianCalendar xmlCal = getPropertyNewValue(propDelta, XMLGregorianCalendar.class);//propDelta.getPropertyNew().getValue(XMLGregorianCalendar.class).getValue(); updateAttributes.add(AttributeBuilder.build(OperationalAttributes.DISABLE_DATE_NAME, xmlCal != null ? XmlTypeConverter.toMillis(xmlCal) : null)); } else if (propDelta.getElementName().equals(ActivationType.F_LOCKOUT_STATUS)) { LockoutStatusType status = getPropertyNewValue(propDelta, LockoutStatusType.class);//propDelta.getPropertyNew().getValue(LockoutStatusType.class).getValue(); updateAttributes.add(AttributeBuilder.build(OperationalAttributes.LOCK_OUT_NAME, status != LockoutStatusType.NORMAL)); } else { throw new SchemaException("Got unknown activation attribute delta " + propDelta.getElementName()); } } } private <T> T getPropertyNewValue(PropertyDelta propertyDelta, Class<T> clazz) throws SchemaException { PrismProperty<PrismPropertyValue<T>> prop = propertyDelta.getPropertyNewMatchingPath(); if (prop == null) { return null; } PrismPropertyValue<T> propValue = prop.getValue(clazz); if (propValue == null) { return null; } return propValue.getValue(); } private void convertFromPassword(Set<Attribute> attributes, PropertyDelta<ProtectedStringType> passwordDelta) throws SchemaException { if (passwordDelta == null) { throw new IllegalArgumentException("No password was provided"); } QName elementName = passwordDelta.getElementName(); if (StringUtils.isBlank(elementName.getNamespaceURI())) { if (!QNameUtil.match(elementName, PasswordType.F_VALUE)) { return; } } else if (!passwordDelta.getElementName().equals(PasswordType.F_VALUE)) { return; } PrismProperty<ProtectedStringType> newPassword = passwordDelta.getPropertyNewMatchingPath(); if (newPassword == null || newPassword.isEmpty()) { // This is the case of setting no password. E.g. removing existing password LOGGER.debug("Setting null password."); attributes.add(AttributeBuilder.build(OperationalAttributes.PASSWORD_NAME, Collections.EMPTY_LIST)); } else if (newPassword.getRealValue().canGetCleartext()) { // We have password and we can get a cleartext value of the passowrd. This is normal case GuardedString guardedPassword = ConnIdUtil.toGuardedString(newPassword.getRealValue(), "new password", protector); attributes.add(AttributeBuilder.build(OperationalAttributes.PASSWORD_NAME, guardedPassword)); } else { // We have password, but we cannot get a cleartext value. Just to nothing. LOGGER.debug( "We would like to set password, but we do not have cleartext value. Skipping the opearation."); } } private void addConvertedValues(Collection<PrismPropertyValue<QName>> pvals, Set<Attribute> attributes, Map<QName, ObjectClassComplexTypeDefinition> auxiliaryObjectClassMap) throws SchemaException { if (pvals == null) { return; } AttributeBuilder ab = new AttributeBuilder(); ab.setName(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME); for (PrismPropertyValue<QName> pval : pvals) { QName auxQName = pval.getValue(); ObjectClassComplexTypeDefinition auxDef = resourceSchema.findObjectClassDefinition(auxQName); if (auxDef == null) { throw new SchemaException("Auxiliary object class " + auxQName + " not found in the schema"); } auxiliaryObjectClassMap.put(auxQName, auxDef); ObjectClass icfOc = connIdNameMapper.objectClassToIcf(pval.getValue(), resourceSchemaNamespace, connectorType, false); ab.addValue(icfOc.getObjectClassValue()); } attributes.add(ab.build()); } private List<Change> getChangesFromSyncDeltas(ObjectClass connIdObjClass, Collection<SyncDelta> connIdDeltas, PrismSchema schema, OperationResult parentResult) throws SchemaException, GenericFrameworkException { List<Change> changeList = new ArrayList<>(); QName objectClass = connIdNameMapper.objectClassToQname(connIdObjClass, getSchemaNamespace(), legacySchema); ObjectClassComplexTypeDefinition objClassDefinition = null; if (objectClass != null) { objClassDefinition = (ObjectClassComplexTypeDefinition) schema.findComplexTypeDefinition(objectClass); } Validate.notNull(connIdDeltas, "Sync result must not be null."); for (SyncDelta icfDelta : connIdDeltas) { ObjectClass deltaIcfObjClass = connIdObjClass; QName deltaObjectClass = objectClass; ObjectClassComplexTypeDefinition deltaObjClassDefinition = objClassDefinition; if (objectClass == null) { deltaIcfObjClass = icfDelta.getObjectClass(); deltaObjectClass = connIdNameMapper.objectClassToQname(deltaIcfObjClass, getSchemaNamespace(), legacySchema); if (deltaIcfObjClass != null) { deltaObjClassDefinition = (ObjectClassComplexTypeDefinition) schema .findComplexTypeDefinition(deltaObjectClass); } } if (deltaObjClassDefinition == null) { if (icfDelta.getDeltaType() == SyncDeltaType.DELETE) { // tolerate this. E.g. LDAP changelogs do not have objectclass in delete deltas. } else { throw new SchemaException("Got delta with object class " + deltaObjectClass + " (" + deltaIcfObjClass + ") that has no definition in resource schema"); } } SyncDeltaType icfDeltaType = icfDelta.getDeltaType(); if (SyncDeltaType.DELETE.equals(icfDeltaType)) { LOGGER.trace("START creating delta of type DELETE"); ObjectDelta<ShadowType> objectDelta = new ObjectDelta<>(ShadowType.class, ChangeType.DELETE, prismContext); Collection<ResourceAttribute<?>> identifiers = ConnIdUtil.convertToIdentifiers(icfDelta.getUid(), deltaObjClassDefinition, resourceSchema); Change change = new Change(identifiers, objectDelta, getToken(icfDelta.getToken())); change.setObjectClassDefinition(deltaObjClassDefinition); changeList.add(change); LOGGER.trace("END creating delta of type DELETE"); } else if (SyncDeltaType.CREATE.equals(icfDeltaType)) { PrismObjectDefinition<ShadowType> objectDefinition = toShadowDefinition(deltaObjClassDefinition); LOGGER.trace("Object definition: {}", objectDefinition); LOGGER.trace("START creating delta of type CREATE"); PrismObject<ShadowType> currentShadow = connIdConvertor.convertToResourceObject( icfDelta.getObject(), objectDefinition, false, caseIgnoreAttributeNames, legacySchema); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Got current shadow: {}", currentShadow.debugDump()); } Collection<ResourceAttribute<?>> identifiers = ShadowUtil.getAllIdentifiers(currentShadow); ObjectDelta<ShadowType> objectDelta = new ObjectDelta<>(ShadowType.class, ChangeType.ADD, prismContext); objectDelta.setObjectToAdd(currentShadow); Change change = new Change(identifiers, objectDelta, getToken(icfDelta.getToken())); change.setObjectClassDefinition(deltaObjClassDefinition); changeList.add(change); LOGGER.trace("END creating delta of type CREATE"); } else if (SyncDeltaType.CREATE_OR_UPDATE.equals(icfDeltaType) || SyncDeltaType.UPDATE.equals(icfDeltaType)) { PrismObjectDefinition<ShadowType> objectDefinition = toShadowDefinition(deltaObjClassDefinition); LOGGER.trace("Object definition: {}", objectDefinition); LOGGER.trace("START creating delta of type {}", icfDeltaType); PrismObject<ShadowType> currentShadow = connIdConvertor.convertToResourceObject( icfDelta.getObject(), objectDefinition, false, caseIgnoreAttributeNames, legacySchema); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Got current shadow: {}", currentShadow.debugDump()); } Collection<ResourceAttribute<?>> identifiers = ShadowUtil.getAllIdentifiers(currentShadow); Change change = new Change(identifiers, currentShadow, getToken(icfDelta.getToken())); change.setObjectClassDefinition(deltaObjClassDefinition); changeList.add(change); LOGGER.trace("END creating delta of type {}:\n{}", icfDeltaType, change.debugDump()); } else { throw new GenericFrameworkException("Unexpected sync delta type " + icfDeltaType); } } return changeList; } private SyncToken getSyncToken(PrismProperty tokenProperty) throws SchemaException { if (tokenProperty == null) { return null; } if (tokenProperty.getValues() == null) { return null; } if (tokenProperty.getValues().isEmpty()) { return null; } if (tokenProperty.getValues().size() > 1) { throw new SchemaException( "Unexpected number of attributes in SyncToken. SyncToken is single-value attribute and can contain only one value."); } Object tokenValue = tokenProperty.getAnyRealValue(); if (tokenValue == null) { return null; } SyncToken syncToken = new SyncToken(tokenValue); return syncToken; } private <T> PrismProperty<T> getToken(SyncToken syncToken) { T object = (T) syncToken.getValue(); return createTokenProperty(object); } private <T> PrismProperty<T> createTokenProperty(T object) { QName type = XsdTypeMapper.toXsdType(object.getClass()); Set<PrismPropertyValue<T>> syncTokenValues = new HashSet<>(); syncTokenValues.add(new PrismPropertyValue<>(object)); PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(SchemaConstants.SYNC_TOKEN, type, prismContext); propDef.setDynamic(true); propDef.setMaxOccurs(1); PrismProperty<T> property = propDef.instantiate(); property.addValues(syncTokenValues); return property; } /** * check additional operation order, according to the order are script * executed before or after operation.. */ private void checkAndExecuteAdditionalOperations(StateReporter reporter, Collection<Operation> additionalOperations, BeforeAfterType order, OperationResult result) throws CommunicationException, GenericFrameworkException { if (additionalOperations == null) { // TODO: add warning to the result return; } for (Operation op : additionalOperations) { if (op instanceof ExecuteProvisioningScriptOperation) { ExecuteProvisioningScriptOperation executeOp = (ExecuteProvisioningScriptOperation) op; LOGGER.trace("Find execute script operation: {}", SchemaDebugUtil.prettyPrint(executeOp)); // execute operation in the right order.. if (order.equals(executeOp.getScriptOrder())) { executeScriptIcf(reporter, executeOp, result); } } } } @Override public Object executeScript(ExecuteProvisioningScriptOperation scriptOperation, StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException { OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".executeScript"); Object output = null; try { output = executeScriptIcf(reporter, scriptOperation, result); } catch (CommunicationException e) { result.recordFatalError(e); throw e; } catch (GenericFrameworkException e) { result.recordFatalError(e); throw e; } catch (RuntimeException e) { result.recordFatalError(e); throw e; } result.computeStatus(); return output; } private Object executeScriptIcf(StateReporter reporter, ExecuteProvisioningScriptOperation scriptOperation, OperationResult parentResult) throws CommunicationException, GenericFrameworkException { String icfOpName = null; if (scriptOperation.isConnectorHost()) { icfOpName = "runScriptOnConnector"; } else if (scriptOperation.isResourceHost()) { icfOpName = "runScriptOnResource"; } else { parentResult.recordFatalError("Where to execute the script?"); throw new IllegalArgumentException("Where to execute the script?"); } // convert execute script operation to the script context required from // the connector ScriptContext scriptContext = convertToScriptContext(scriptOperation); OperationResult icfResult = parentResult.createSubresult(ConnectorFacade.class.getName() + "." + icfOpName); icfResult.addContext("connector", connIdConnectorFacade.getClass()); Object output = null; try { LOGGER.trace("Running script ({})", icfOpName); recordIcfOperationStart(reporter, ProvisioningOperation.ICF_SCRIPT, null); if (scriptOperation.isConnectorHost()) { InternalMonitor.recordConnectorOperation("runScriptOnConnector"); output = connIdConnectorFacade.runScriptOnConnector(scriptContext, new OperationOptionsBuilder().build()); } else if (scriptOperation.isResourceHost()) { InternalMonitor.recordConnectorOperation("runScriptOnResource"); output = connIdConnectorFacade.runScriptOnResource(scriptContext, new OperationOptionsBuilder().build()); } recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SCRIPT, null); icfResult.recordSuccess(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Finished running script ({}), script result: {}", icfOpName, PrettyPrinter.prettyPrint(output)); } } catch (Throwable ex) { recordIcfOperationEnd(reporter, ProvisioningOperation.ICF_SCRIPT, null, ex); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Finished running script ({}), ERROR: {}", icfOpName, ex.getMessage()); } Throwable midpointEx = processConnIdException(ex, this, icfResult); CriticalityType criticality = scriptOperation.getCriticality(); if (criticality == null || criticality == CriticalityType.FATAL) { parentResult.computeStatus(); // Do some kind of acrobatics to do proper throwing of checked // exception if (midpointEx instanceof CommunicationException) { throw (CommunicationException) midpointEx; } else if (midpointEx instanceof GenericFrameworkException) { throw (GenericFrameworkException) midpointEx; } else if (midpointEx instanceof SchemaException) { // Schema exception during delete? It must be a missing UID throw new IllegalArgumentException(midpointEx.getMessage(), midpointEx); } else if (midpointEx instanceof RuntimeException) { throw (RuntimeException) midpointEx; } else if (midpointEx instanceof Error) { throw (Error) midpointEx; } else { throw new SystemException( "Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex); } } else if (criticality == CriticalityType.PARTIAL) { icfResult.setStatus(OperationResultStatus.PARTIAL_ERROR); parentResult.computeStatus(); } } return output; } private ScriptContext convertToScriptContext(ExecuteProvisioningScriptOperation executeOp) { // creating script arguments map form the execute script operation // arguments Map<String, Object> scriptArguments = new HashMap<>(); for (ExecuteScriptArgument argument : executeOp.getArgument()) { scriptArguments.put(argument.getArgumentName(), argument.getArgumentValue()); } ScriptContext scriptContext = new ScriptContext(executeOp.getLanguage(), executeOp.getTextCode(), scriptArguments); return scriptContext; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "ConnectorInstanceIcfImpl(" + connectorType + ")"; } public String getHumanReadableName() { return connectorType.toString() + ": " + description; } @Override public void dispose() { // Nothing to do } private void recordIcfOperationStart(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition, Uid uid) { if (reporter != null) { reporter.recordIcfOperationStart(operation, objectClassDefinition, uid == null ? null : uid.getUidValue()); } else { LOGGER.warn("Couldn't record ConnId operation start as reporter is null."); } } private void recordIcfOperationStart(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition) { if (reporter != null) { reporter.recordIcfOperationStart(operation, objectClassDefinition, null); } else { LOGGER.warn("Couldn't record ConnId operation start as reporter is null."); } } private void recordIcfOperationResume(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition) { if (reporter != null) { reporter.recordIcfOperationResume(operation, objectClassDefinition); } else { LOGGER.warn("Couldn't record ConnId operation resume as reporter is null."); } } private void recordIcfOperationSuspend(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition) { if (reporter != null) { reporter.recordIcfOperationSuspend(operation, objectClassDefinition); } else { LOGGER.warn("Couldn't record ConnId operation suspension as reporter is null."); } } private void recordIcfOperationEnd(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition, Uid uid) { if (reporter != null) { reporter.recordIcfOperationEnd(operation, objectClassDefinition, null, uid == null ? null : uid.getUidValue()); } else { LOGGER.warn("Couldn't record ConnId operation end as reporter is null."); } } private void recordIcfOperationEnd(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition, Throwable ex) { if (reporter != null) { reporter.recordIcfOperationEnd(operation, objectClassDefinition, ex, null); } else { LOGGER.warn("Couldn't record ConnId operation end as reporter is null."); } } private void recordIcfOperationEnd(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition, Throwable ex, Uid uid) { if (reporter != null) { reporter.recordIcfOperationEnd(operation, objectClassDefinition, ex, uid == null ? null : uid.getUidValue()); } else { LOGGER.warn("Couldn't record ConnId operation end as reporter is null."); } } private void recordIcfOperationEnd(StateReporter reporter, ProvisioningOperation operation, ObjectClassComplexTypeDefinition objectClassDefinition) { if (reporter != null) { reporter.recordIcfOperationEnd(operation, objectClassDefinition, null, null); } else { LOGGER.warn("Couldn't record ConnId operation end as reporter is null."); } } }