/** * Copyright (c) 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.builtin; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.xml.namespace.QName; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Component; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContainerDefinitionImpl; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.schema.PrismSchema; import com.evolveum.midpoint.prism.schema.PrismSchemaImpl; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.provisioning.ucf.api.ConfigurationProperty; import com.evolveum.midpoint.provisioning.ucf.api.ConnectorFactory; import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; import com.evolveum.midpoint.provisioning.ucf.api.ManagedConnector; import com.evolveum.midpoint.provisioning.ucf.api.ManagedConnectorConfiguration; import com.evolveum.midpoint.provisioning.ucf.api.UcfUtil; import com.evolveum.midpoint.provisioning.ucf.api.connectors.AbstractManagedConnectorInstance; import com.evolveum.midpoint.repo.api.RepositoryAware; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorHostType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; /** * Connector factory for the connectors built-in to midPoint, such as * the "manual connector". * * @author Radovan Semancik * */ @Component public class ConnectorFactoryBuiltinImpl implements ConnectorFactory { public static final String SCAN_PACKAGE = "com.evolveum.midpoint"; private static final String CONFIGURATION_NAMESPACE_PREFIX = SchemaConstants.UCF_FRAMEWORK_URI_BUILTIN + "/bundle/"; private static final Trace LOGGER = TraceManager.getTrace(ConnectorFactoryBuiltinImpl.class); @Autowired(required=true) private PrismContext prismContext; @Autowired(required = true) @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; private Map<String,ConnectorStruct> connectorMap; @Override public Set<ConnectorType> listConnectors(ConnectorHostType host, OperationResult parentRestul) throws CommunicationException { if (connectorMap == null) { discoverConnectors(); } return connectorMap.entrySet().stream().map(e -> e.getValue().connectorObject).collect(Collectors.toSet()); } private void discoverConnectors() { connectorMap = new HashMap<>(); ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(ManagedConnector.class)); LOGGER.trace("Scanning package {}", SCAN_PACKAGE); for (BeanDefinition bd : scanner.findCandidateComponents(SCAN_PACKAGE)) { LOGGER.debug("Found connector class {}", bd); String beanClassName = bd.getBeanClassName(); try { Class connectorClass = Class.forName(beanClassName); ManagedConnector annotation = (ManagedConnector) connectorClass.getAnnotation(ManagedConnector.class); String type = annotation.type(); LOGGER.debug("Found connector {} class {}", type, connectorClass); ConnectorStruct struct = createConnectorStruct(connectorClass, annotation); connectorMap.put(type, struct); } catch (ClassNotFoundException e) { LOGGER.error("Error loading connector class {}: {}", beanClassName, e.getMessage(), e); } catch (ObjectNotFoundException | SchemaException e) { LOGGER.error("Error discovering the connector {}: {}", beanClassName, e.getMessage(), e); } } LOGGER.trace("Scan done"); } private ConnectorStruct createConnectorStruct(Class connectorClass, ManagedConnector annotation) throws ObjectNotFoundException, SchemaException { ConnectorStruct struct = new ConnectorStruct(); struct.connectorClass = connectorClass; ConnectorType connectorType = new ConnectorType(); String bundleName = connectorClass.getPackage().getName(); String type = annotation.type(); if (type == null || type.isEmpty()) { type = connectorClass.getSimpleName(); } String version = annotation.version(); UcfUtil.addConnectorNames(connectorType, "Built-in", bundleName, type, version, null); connectorType.setConnectorBundle(bundleName); connectorType.setConnectorType(type); connectorType.setVersion(version); connectorType.setFramework(SchemaConstants.UCF_FRAMEWORK_URI_BUILTIN); String namespace = CONFIGURATION_NAMESPACE_PREFIX + bundleName + "/" + type; connectorType.setNamespace(namespace); struct.connectorObject = connectorType; PrismSchema connectorSchema = generateConnectorConfigurationSchema(struct); if (connectorSchema != null) { LOGGER.trace("Generated connector schema for {}: {} definitions", connectorType, connectorSchema.getDefinitions().size()); UcfUtil.setConnectorSchema(connectorType, connectorSchema); struct.connectorConfigurationSchema = connectorSchema; } else { LOGGER.warn("No connector schema generated for {}", connectorType); } return struct; } private ConnectorStruct getConnectorStruct(ConnectorType connectorType) throws ObjectNotFoundException { if (connectorMap == null) { discoverConnectors(); } String type = connectorType.getConnectorType(); ConnectorStruct struct = connectorMap.get(type); if (struct == null) { throw new ObjectNotFoundException("No built-in connector type "+type); } return struct; } @Override public PrismSchema generateConnectorConfigurationSchema(ConnectorType connectorType) throws ObjectNotFoundException { ConnectorStruct struct = getConnectorStruct(connectorType); return generateConnectorConfigurationSchema(struct); } private PrismSchema generateConnectorConfigurationSchema(ConnectorStruct struct) { Class<? extends ConnectorInstance> connectorClass = struct.connectorClass; PropertyDescriptor connectorConfigurationProp = UcfUtil.findAnnotatedProperty(connectorClass, ManagedConnectorConfiguration.class); PrismSchema connectorSchema = new PrismSchemaImpl(struct.connectorObject.getNamespace(), prismContext); // Create configuration type - the type used by the "configuration" element PrismContainerDefinitionImpl<?> configurationContainerDef = ((PrismSchemaImpl) connectorSchema).createPropertyContainerDefinition( ResourceType.F_CONNECTOR_CONFIGURATION.getLocalPart(), SchemaConstants.CONNECTOR_SCHEMA_CONFIGURATION_TYPE_LOCAL_NAME); Class<?> configurationClass = connectorConfigurationProp.getPropertyType(); BeanWrapper configurationClassBean = new BeanWrapperImpl(configurationClass); for (PropertyDescriptor prop: configurationClassBean.getPropertyDescriptors()) { if (!UcfUtil.hasAnnotation(prop, ConfigurationProperty.class)) { continue; } ItemDefinition<?> itemDef = createPropertyDefinition(configurationContainerDef, prop); LOGGER.trace("Configuration item definition for {}: {}", prop.getName(), itemDef); } return connectorSchema; } private ItemDefinition<?> createPropertyDefinition(PrismContainerDefinitionImpl<?> configurationContainerDef, PropertyDescriptor prop) { String propName = prop.getName(); Class<?> propertyType = prop.getPropertyType(); Class<?> baseType = propertyType; int minOccurs = 1; int maxOccurs = 1; if (propertyType.isArray()) { maxOccurs = -1; baseType = propertyType.getComponentType(); } // TODO: minOccurs: define which properties are optional/mandatory // TODO: display names, ordering, help texts QName propType = XsdTypeMapper.toXsdType(baseType); return configurationContainerDef.createPropertyDefinition(new QName(configurationContainerDef.getName().getNamespaceURI(), propName), propType, minOccurs, maxOccurs); } @Override public ConnectorInstance createConnectorInstance(ConnectorType connectorType, String namespace, String desc) throws ObjectNotFoundException, SchemaException { String type = connectorType.getConnectorType(); ConnectorStruct struct = connectorMap.get(type); Class<? extends ConnectorInstance> connectorClass = struct.connectorClass; ConnectorInstance connectorInstance; try { connectorInstance = connectorClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new ObjectNotFoundException("Cannot create instance of connector "+connectorClass+": "+e.getMessage(), e); } if (connectorInstance instanceof AbstractManagedConnectorInstance) { setupAbstractConnectorInstance((AbstractManagedConnectorInstance)connectorInstance, connectorType, namespace, desc, struct); } if (connectorInstance instanceof RepositoryAware) { ((RepositoryAware)connectorInstance).setRepositoryService(repositoryService); } return connectorInstance; } private void setupAbstractConnectorInstance(AbstractManagedConnectorInstance connectorInstance, ConnectorType connectorObject, String namespace, String desc, ConnectorStruct struct) { connectorInstance.setConnectorObject(connectorObject); connectorInstance.setResourceSchemaNamespace(namespace); connectorInstance.setPrismContext(prismContext); connectorInstance.setConnectorConfigurationSchema(struct.connectorConfigurationSchema); } @Override public void selfTest(OperationResult parentTestResult) { // Nothing to do } @Override public boolean supportsFramework(String frameworkIdentifier) { return SchemaConstants.UCF_FRAMEWORK_URI_BUILTIN.equals(frameworkIdentifier); } @Override public String getFrameworkVersion() { return "1.0.0"; } @Override public void shutdown() { // Nothing to do } private class ConnectorStruct { Class<? extends ConnectorInstance> connectorClass; ConnectorType connectorObject; PrismSchema connectorConfigurationSchema; } }