Java tutorial
/* * Copyright (c) 2010-2015 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; import static com.evolveum.midpoint.provisioning.ucf.impl.IcfUtil.processIcfException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.security.Key; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.annotation.PostConstruct; import javax.net.ssl.TrustManager; import javax.xml.namespace.QName; import org.apache.commons.configuration.Configuration; import org.identityconnectors.common.Version; import org.identityconnectors.common.security.Encryptor; import org.identityconnectors.common.security.EncryptorFactory; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.api.ConnectorFacadeFactory; import org.identityconnectors.framework.api.ConnectorInfo; import org.identityconnectors.framework.api.ConnectorInfoManager; import org.identityconnectors.framework.api.ConnectorInfoManagerFactory; import org.identityconnectors.framework.api.ConnectorKey; import org.identityconnectors.framework.api.RemoteFrameworkConnectionInfo; import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.api.operations.AuthenticationApiOp; 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.api.operations.ValidateApiOp; import org.identityconnectors.framework.common.FrameworkUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.w3c.dom.Element; import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.schema.PrismSchema; import com.evolveum.midpoint.provisioning.ucf.api.ConnectorFactory; import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; 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.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.ConnectorHostType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.XmlSchemaType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** * Currently the only implementation of the UCF Connector Manager API interface. * * It is hardcoded to ICF now. * * This class holds a list of all known ICF connectors in the system. * * @author Radovan Semancik */ @Component public class ConnectorFactoryIcfImpl implements ConnectorFactory { public static final String ICF_FRAMEWORK_URI = "http://midpoint.evolveum.com/xml/ns/public/connector/icf-1"; public static final String NS_ICF_CONFIGURATION = ICF_FRAMEWORK_URI + "/connector-schema-3"; // Note! This is also specified in SchemaConstants (MID-356) public static final String NS_ICF_SCHEMA = ICF_FRAMEWORK_URI + "/resource-schema-3"; public static final String NS_ICF_SCHEMA_PREFIX = "icfs"; public static final String NS_ICF_RESOURCE_INSTANCE_PREFIX = "ri"; public static final QName ICFS_NAME = new QName(NS_ICF_SCHEMA, "name"); public static final String ICFS_NAME_DISPLAY_NAME = "ConnId Name"; public static final int ICFS_NAME_DISPLAY_ORDER = 110; public static final QName ICFS_UID = new QName(NS_ICF_SCHEMA, "uid"); public static final String ICFS_UID_DISPLAY_NAME = "ConnId UID"; public static final int ICFS_UID_DISPLAY_ORDER = 100; public static final QName ICFS_ACCOUNT = new QName(NS_ICF_SCHEMA, "account"); public static final String ACCOUNT_OBJECT_CLASS_LOCAL_NAME = "AccountObjectClass"; public static final String GROUP_OBJECT_CLASS_LOCAL_NAME = "GroupObjectClass"; public static final String CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_LOCAL_NAME = "configurationProperties"; public static final QName CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_QNAME = new QName( NS_ICF_CONFIGURATION, CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_LOCAL_NAME); public static final String CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_LOCAL_NAME = "ConfigurationPropertiesType"; public static final QName CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_QNAME = new QName(NS_ICF_CONFIGURATION, CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_LOCAL_NAME); public static final String CONNECTOR_SCHEMA_CONFIGURATION_TYPE_LOCAL_NAME = "ConfigurationType"; public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_XML_ELEMENT_NAME = "connectorPoolConfiguration"; public static final QName CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_ELEMENT = new QName( NS_ICF_CONFIGURATION, "connectorPoolConfiguration"); public static final QName CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_TYPE = new QName(NS_ICF_CONFIGURATION, "ConnectorPoolConfigurationType"); protected static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis"; public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MIN_IDLE = "minIdle"; public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_IDLE = "maxIdle"; public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_OBJECTS = "maxObjects"; public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_WAIT = "maxWait"; public static final String CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_XML_ELEMENT_NAME = "producerBufferSize"; public static final QName CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_ELEMENT = new QName(NS_ICF_CONFIGURATION, CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_XML_ELEMENT_NAME); public static final QName CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_TYPE = DOMUtil.XSD_INT; public static final String CONNECTOR_SCHEMA_LEGACY_SCHEMA_XML_ELEMENT_NAME = "legacySchema"; public static final QName CONNECTOR_SCHEMA_LEGACY_SCHEMA_ELEMENT = new QName(NS_ICF_CONFIGURATION, CONNECTOR_SCHEMA_LEGACY_SCHEMA_XML_ELEMENT_NAME); public static final QName CONNECTOR_SCHEMA_LEGACY_SCHEMA_TYPE = DOMUtil.XSD_BOOLEAN; public static final String CONNECTOR_SCHEMA_TIMEOUTS_XML_ELEMENT_NAME = "timeouts"; public static final QName CONNECTOR_SCHEMA_TIMEOUTS_ELEMENT = new QName(NS_ICF_CONFIGURATION, CONNECTOR_SCHEMA_TIMEOUTS_XML_ELEMENT_NAME); public static final QName CONNECTOR_SCHEMA_TIMEOUTS_TYPE = new QName(NS_ICF_CONFIGURATION, "TimeoutsType"); public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME = "resultsHandlerConfiguration"; public static final QName CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT = new QName( NS_ICF_CONFIGURATION, CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME); public static final QName CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_TYPE = new QName(NS_ICF_CONFIGURATION, "ResultsHandlerConfigurationType"); public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_NORMALIZING_RESULTS_HANDLER = "enableNormalizingResultsHandler"; public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_FILTERED_RESULTS_HANDLER = "enableFilteredResultsHandler"; public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_FILTERED_RESULTS_HANDLER_IN_VALIDATION_MODE = "filteredResultsHandlerInValidationMode"; public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_CASE_INSENSITIVE_HANDLER = "enableCaseInsensitiveFilter"; public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_ATTRIBUTES_TO_GET_SEARCH_RESULTS_HANDLER = "enableAttributesToGetSearchResultsHandler"; static final Map<String, Class<? extends APIOperation>> apiOpMap = new HashMap<String, Class<? extends APIOperation>>(); private static final String ICF_CONFIGURATION_NAMESPACE_PREFIX = ICF_FRAMEWORK_URI + "/bundle/"; private static final String CONNECTOR_IDENTIFIER_SEPARATOR = "/"; public static final int ATTR_DISPLAY_ORDER_START = 120; public static final int ATTR_DISPLAY_ORDER_INCREMENT = 10; private static final Trace LOGGER = TraceManager.getTrace(ConnectorFactoryIcfImpl.class); // This is not really used in the code. It is here just to make sure that the JUL logger is loaded // by the parent classloader so we can correctly adjust the log levels from the main code static final java.util.logging.Logger JUL_LOGGER = java.util.logging.Logger .getLogger(ConnectorFactoryIcfImpl.class.getName()); private ConnectorInfoManagerFactory connectorInfoManagerFactory; private ConnectorInfoManager localConnectorInfoManager; private Set<URL> bundleURLs; private Set<ConnectorType> localConnectorTypes = null; @Autowired(required = true) MidpointConfiguration midpointConfiguration; @Autowired(required = true) Protector protector; @Autowired(required = true) PrismContext prismContext; public ConnectorFactoryIcfImpl() { } /** * Initialize the ICF implementation. Look for all connector bundles, get * basic information about them and keep that in memory. */ @PostConstruct public void initialize() { // OLD // bundleURLs = listBundleJars(); bundleURLs = new HashSet<URL>(); Configuration config = midpointConfiguration.getConfiguration("midpoint.icf"); // Is classpath scan enabled if (config.getBoolean("scanClasspath")) { // Scan class path bundleURLs.addAll(scanClassPathForBundles()); } // Scan all provided directories @SuppressWarnings("unchecked") List<String> dirs = config.getList("scanDirectory"); for (String dir : dirs) { bundleURLs.addAll(scanDirectory(dir)); } for (URL u : bundleURLs) { LOGGER.debug("ICF bundle URL : {}", u); } connectorInfoManagerFactory = ConnectorInfoManagerFactory.getInstance(); } /** * Creates new connector instance. * * It will initialize the connector by taking the XML Resource definition, * transforming it to the ICF configuration and applying that to the new * connector instance. * * @throws ObjectNotFoundException * @throws SchemaException * */ @Override public ConnectorInstance createConnectorInstance(ConnectorType connectorType, String namespace, String desc) throws ObjectNotFoundException, SchemaException { ConnectorInfo cinfo = getConnectorInfo(connectorType); if (cinfo == null) { LOGGER.error("Failed to instantiate {}", ObjectTypeUtil.toShortString(connectorType)); LOGGER.debug("Connector key: {}, host: {}", getConnectorKey(connectorType), ObjectTypeUtil.toShortString(connectorType)); LOGGER.trace("Connector object: {}", ObjectTypeUtil.dump(connectorType)); LOGGER.trace("Connector host object: {}", ObjectTypeUtil.dump(connectorType.getConnectorHost())); throw new ObjectNotFoundException("The classes (JAR) of " + ObjectTypeUtil.toShortString(connectorType) + " were not found by the ICF framework; bundle=" + connectorType.getConnectorBundle() + " connector type=" + connectorType.getConnectorType() + ", version=" + connectorType.getConnectorVersion()); } PrismSchema connectorSchema = getConnectorSchema(connectorType, namespace); // Create new midPoint ConnectorInstance and pass it the ICF connector // facade ConnectorInstanceIcfImpl connectorImpl = new ConnectorInstanceIcfImpl(cinfo, connectorType, namespace, connectorSchema, protector, prismContext); connectorImpl.setDescription(desc); return connectorImpl; } private PrismSchema getConnectorSchema(ConnectorType connectorType, String namespace) throws SchemaException { XmlSchemaType xmlSchema = connectorType.getSchema(); if (xmlSchema == null) { return null; } Element xsdElement = ObjectTypeUtil.findXsdElement(xmlSchema); if (xsdElement == null) { return null; } PrismSchema connectorSchema = PrismSchema.parse(xsdElement, true, connectorType.toString(), prismContext); return connectorSchema; } /** * Returns a list XML representation of the ICF connectors. * * @throws CommunicationException */ @Override public Set<ConnectorType> listConnectors(ConnectorHostType host, OperationResult parentRestul) throws CommunicationException { OperationResult result = parentRestul.createSubresult(ConnectorFactory.OPERATION_LIST_CONNECTOR); result.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, ConnectorFactoryIcfImpl.class); result.addParam("host", host); try { if (host == null) { Set<ConnectorType> connectors = listLocalConnectors(); result.recordSuccess(); return connectors; } else { // This is necessary as list of the remote connectors is cached locally. // So if any remote connector is added then it will not be discovered unless we // clear the cache. This may look like inefficiency but in fact the listConnectors() method is // used only when discovering new connectors. Normal connector operation is using connector objects // stored in repository. connectorInfoManagerFactory.clearRemoteCache(); Set<ConnectorType> connectors = listRemoteConnectors(host); result.recordSuccess(); return connectors; } } catch (Throwable icfException) { Throwable ex = processIcfException(icfException, "list connectors", result); result.recordFatalError(ex.getMessage(), ex); if (ex instanceof CommunicationException) { throw (CommunicationException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else if (ex instanceof Error) { throw (Error) ex; } else { throw new SystemException("Unexpected ICF exception: " + ex.getMessage(), ex); } } } private Set<ConnectorType> listLocalConnectors() { if (localConnectorTypes == null) { // Lazy initialize connector list localConnectorTypes = new HashSet<ConnectorType>(); // Fetch list of local connectors from ICF List<ConnectorInfo> connectorInfos = getLocalConnectorInfoManager().getConnectorInfos(); for (ConnectorInfo connectorInfo : connectorInfos) { ConnectorType connectorType = convertToConnectorType(connectorInfo, null); localConnectorTypes.add(connectorType); } } return localConnectorTypes; } private Set<ConnectorType> listRemoteConnectors(ConnectorHostType host) { ConnectorInfoManager remoteConnectorInfoManager = getRemoteConnectorInfoManager(host); Set<ConnectorType> connectorTypes = new HashSet<ConnectorType>(); List<ConnectorInfo> connectorInfos = remoteConnectorInfoManager.getConnectorInfos(); for (ConnectorInfo connectorInfo : connectorInfos) { ConnectorType connectorType = convertToConnectorType(connectorInfo, host); connectorTypes.add(connectorType); } return connectorTypes; } /** * Converts ICF ConnectorInfo into a midPoint XML connector representation. * * TODO: schema transformation * * @param hostType * host that this connector runs on or null for local connectors */ private ConnectorType convertToConnectorType(ConnectorInfo cinfo, ConnectorHostType hostType) { ConnectorType connectorType = new ConnectorType(); ConnectorKey key = cinfo.getConnectorKey(); String stringID = keyToNamespaceSuffix(key); StringBuilder connectorName = new StringBuilder("ICF "); connectorName.append(key.getConnectorName()); connectorName.append(" v"); connectorName.append(key.getBundleVersion()); if (hostType != null) { connectorName.append(" @"); connectorName.append(hostType.getName()); } connectorType.setName(new PolyStringType(connectorName.toString())); connectorType.setFramework(ICF_FRAMEWORK_URI); connectorType.setConnectorType(key.getConnectorName()); connectorType.setNamespace(ICF_CONFIGURATION_NAMESPACE_PREFIX + stringID); connectorType.setConnectorVersion(key.getBundleVersion()); connectorType.setConnectorBundle(key.getBundleName()); if (hostType != null) { if (hostType.getOid() != null) { // bind using connectorHostRef and OID ObjectReferenceType ref = new ObjectReferenceType(); ref.setOid(hostType.getOid()); ref.setType(ObjectTypes.CONNECTOR_HOST.getTypeQName()); connectorType.setConnectorHostRef(ref); } else { // Embed the object connectorType.setConnectorHost(hostType); } } return connectorType; } /** * Converts ICF connector key to a simple string. * * The string may be used as an OID. */ private String keyToNamespaceSuffix(ConnectorKey key) { StringBuilder sb = new StringBuilder(); sb.append(key.getBundleName()); sb.append(CONNECTOR_IDENTIFIER_SEPARATOR); // Don't include version. It is lesser evil. // sb.append(key.getBundleVersion()); // sb.append(CONNECTOR_IDENTIFIER_SEPARATOR); sb.append(key.getConnectorName()); return sb.toString(); } /** * Returns ICF connector info manager that manages local connectors. The * manager will be created if it does not exist yet. * * @return ICF connector info manager that manages local connectors */ private ConnectorInfoManager getLocalConnectorInfoManager() { if (null == localConnectorInfoManager) { localConnectorInfoManager = connectorInfoManagerFactory.getLocalManager(bundleURLs.toArray(new URL[0])); } return localConnectorInfoManager; } /** * Returns ICF connector info manager that manages local connectors. The * manager will be created if it does not exist yet. * * @return ICF connector info manager that manages local connectors */ private ConnectorInfoManager getRemoteConnectorInfoManager(ConnectorHostType hostType) { String hostname = hostType.getHostname(); int port = Integer.parseInt(hostType.getPort()); GuardedString key; try { key = new GuardedString(protector.decryptString(hostType.getSharedSecret()).toCharArray()); } catch (EncryptionException e) { throw new SystemException("Shared secret decryption error: " + e.getMessage(), e); } Integer timeout = hostType.getTimeout(); if (timeout == null) { timeout = 0; } boolean useSSL = false; if (hostType.isProtectConnection() != null) { useSSL = hostType.isProtectConnection(); } List<TrustManager> trustManagers = protector.getTrustManagers(); LOGGER.trace( "Creating RemoteFrameworkConnectionInfo: hostname={}, port={}, key={}, useSSL={}, trustManagers={}, timeout={}", new Object[] { hostname, port, key, useSSL, trustManagers, timeout }); RemoteFrameworkConnectionInfo remoteFramewrorkInfo = new RemoteFrameworkConnectionInfo(hostname, port, key, useSSL, trustManagers, timeout); return connectorInfoManagerFactory.getRemoteManager(remoteFramewrorkInfo); } /** * Scan class path for connector bundles * * @return Set of all bundle URL */ private Set<URL> scanClassPathForBundles() { Set<URL> bundle = new HashSet<URL>(); // scan class path for bundles Enumeration<URL> en = null; try { // Search all jars in classpath en = ConnectorFactoryIcfImpl.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); } catch (IOException ex) { LOGGER.debug("Error during reding content from class path"); } // Find which one is ICF connector while (en.hasMoreElements()) { URL u = en.nextElement(); Properties prop = new Properties(); LOGGER.trace("Scan classloader resource: " + u.toString()); try { // read content of META-INF/MANIFEST.MF InputStream is = u.openStream(); // skip if unreadable if (is == null) { continue; } // Convert to properties prop.load(is); } catch (IOException ex) { LOGGER.trace("Unable load: " + u + " [" + ex.getMessage() + "]"); } if (null != prop.get("ConnectorBundle-Name")) { LOGGER.info("Discovered ICF bundle on CLASSPATH: " + prop.get("ConnectorBundle-Name") + " version: " + prop.get("ConnectorBundle-Version")); // hack to split MANIFEST from name try { URL tmp = new URL(toUrl(u.getPath().split("!")[0])); if (isThisBundleCompatible(tmp)) { bundle.add(tmp); } else { LOGGER.warn("Skip loading ICF bundle {} due error occured", tmp); } } catch (MalformedURLException e) { LOGGER.error("This never happend we hope. URL:" + u.getPath(), e); throw new SystemException(e); } } } return bundle; } private String toUrl(String string) { if (string.contains(":")) { // We are OK, there is a protocol section return string; } // We assume file protocol as default return "file:" + string; } /** * Scan directory for bundles only on first lval we do the scan * * @param path * @return */ private Set<URL> scanDirectory(String path) { // Prepare return object Set<URL> bundle = new HashSet<URL>(); // COnvert path to object File File dir = new File(path); // Test if this path is single jar or need to do deep examination if (isThisJarFileBundle(dir)) { try { if (isThisBundleCompatible(dir.toURI().toURL())) { bundle.add(dir.toURI().toURL()); } else { LOGGER.warn("Skip loading budle {} due error occured", dir.toURI().toURL()); } } catch (MalformedURLException e) { LOGGER.error("This never happend we hope.", e); throw new SystemException(e); } return bundle; } // Test if it is a directory if (!dir.isDirectory()) { LOGGER.error("Provided Icf connector path {} is not a directory.", dir.getAbsolutePath()); } // List directory items File[] dirEntries = dir.listFiles(); if (null == dirEntries) { LOGGER.warn("No bundles found in directory {}", dir.getAbsolutePath()); return bundle; } // test all entires for bundle for (int i = 0; i < dirEntries.length; i++) { if (isThisJarFileBundle(dirEntries[i])) { try { if (isThisBundleCompatible(dirEntries[i].toURI().toURL())) { bundle.add(dirEntries[i].toURI().toURL()); } else { LOGGER.warn("Skip loading budle {} due error occured", dirEntries[i].toURI().toURL()); } } catch (MalformedURLException e) { LOGGER.error("This never happend we hope.", e); throw new SystemException(e); } } } return bundle; } /** * Test if bundle internal configuration and dependencies are OK * @param bundleUrl * tested bundle URL * @return true if OK */ private Boolean isThisBundleCompatible(URL bundleUrl) { if (null == bundleUrl) return false; try { ConnectorInfoManager localManager = ConnectorInfoManagerFactory.getInstance() .getLocalManager(bundleUrl); List<ConnectorInfo> connectorInfos = localManager.getConnectorInfos(); if (connectorInfos == null || connectorInfos.isEmpty()) { LOGGER.error( "Strange error happened. ConnId is not accepting bundle {}. But no error is indicated.", bundleUrl); return false; } else { LOGGER.trace("Found {} compatible connectors in bundle {}", connectorInfos.size(), bundleUrl); return true; } } catch (Exception ex) { if (LOGGER.isDebugEnabled()) { LOGGER.error("Error instantiating ICF bundle using URL '{}': {}", new Object[] { bundleUrl, ex.getMessage(), ex }); } else { LOGGER.error("Error instantiating ICF bundle using URL '{}': {}", new Object[] { bundleUrl, ex.getMessage() }); } return false; } } /** * Test if provided file is connector bundle * * @param file * tested file * @return boolean */ private Boolean isThisJarFileBundle(File file) { // Startup tests if (null == file) { throw new IllegalArgumentException("No file is providied for bundle test."); } // Skip all processing if it is not a file if (!file.isFile()) { LOGGER.debug("This {} is not a file", file.getAbsolutePath()); return false; } Properties prop = new Properties(); JarFile jar = null; // Open jar file try { jar = new JarFile(file); } catch (IOException ex) { LOGGER.debug("Unable to read jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]"); return false; } // read jar file InputStream is; try { JarEntry entry = new JarEntry("META-INF/MANIFEST.MF"); is = jar.getInputStream(entry); } catch (IOException ex) { LOGGER.debug("Unable to fine MANIFEST.MF in jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]"); return false; } // Parse jar file try { prop.load(is); } catch (IOException ex) { LOGGER.debug("Unable to parse jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]"); return false; } catch (NullPointerException ex) { LOGGER.debug("Unable to parse jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]"); return false; } // Test if it is a connector if (null != prop.get("ConnectorBundle-Name")) { LOGGER.info("Discovered ICF bundle in JAR: " + prop.get("ConnectorBundle-Name") + " version: " + prop.get("ConnectorBundle-Version")); return true; } LOGGER.debug("Provided file {} is not iCF bundle jar", file.getAbsolutePath()); return false; } /** * Get contect informations * * @param connectorType * @return * @throws ObjectNotFoundException */ private ConnectorInfo getConnectorInfo(ConnectorType connectorType) throws ObjectNotFoundException { if (!ICF_FRAMEWORK_URI.equals(connectorType.getFramework())) { throw new ObjectNotFoundException("Requested connector for framework " + connectorType.getFramework() + " cannot be found in framework " + ICF_FRAMEWORK_URI); } ConnectorKey key = getConnectorKey(connectorType); if (connectorType.getConnectorHost() == null && connectorType.getConnectorHostRef() == null) { // Local connector return getLocalConnectorInfoManager().findConnectorInfo(key); } ConnectorHostType host = connectorType.getConnectorHost(); if (host == null) { throw new ObjectNotFoundException( "Attempt to use remote connector without ConnectorHostType resolved (there is only ConnectorHostRef"); } return getRemoteConnectorInfoManager(host).findConnectorInfo(key); } private ConnectorKey getConnectorKey(ConnectorType connectorType) { return new ConnectorKey(connectorType.getConnectorBundle(), connectorType.getConnectorVersion(), connectorType.getConnectorType()); } @Override public void selfTest(OperationResult parentTestResult) { selfTestGuardedString(parentTestResult); } @Override public String getFrameworkVersion() { Version version = FrameworkUtil.getFrameworkVersion(); if (version == null) { return null; } return version.getVersion(); } private void selfTestGuardedString(OperationResult parentTestResult) { OperationResult result = parentTestResult .createSubresult(ConnectorFactoryIcfImpl.class + ".selfTestGuardedString"); OperationResult subresult = result .createSubresult(ConnectorFactoryIcfImpl.class + ".selfTestGuardedString.encryptorReflection"); EncryptorFactory encryptorFactory = EncryptorFactory.getInstance(); subresult.addReturn("encryptorFactoryImpl", encryptorFactory.getClass()); LOGGER.debug("Encryptor factory implementation class: {}", encryptorFactory.getClass()); Encryptor encryptor = EncryptorFactory.getInstance().newRandomEncryptor(); subresult.addReturn("encryptorImpl", encryptor.getClass()); LOGGER.debug("Encryptor implementation class: {}", encryptor.getClass()); if (encryptor.getClass().getName().equals("org.identityconnectors.common.security.impl.EncryptorImpl")) { // let's do some reflection magic to have a look inside try { LOGGER.trace("Encryptor fields: {}", Arrays.asList(encryptor.getClass().getDeclaredFields())); Field keyField = encryptor.getClass().getDeclaredField("key"); keyField.setAccessible(true); Key key = (Key) keyField.get(encryptor); subresult.addReturn("keyAlgorithm", key.getAlgorithm()); subresult.addReturn("keyLength", key.getEncoded().length * 8); subresult.addReturn("keyFormat", key.getFormat()); subresult.recordSuccess(); } catch (IllegalArgumentException e) { subresult.recordPartialError("Reflection introspection failed", e); } catch (IllegalAccessException e) { subresult.recordPartialError("Reflection introspection failed", e); } catch (NoSuchFieldException e) { subresult.recordPartialError("Reflection introspection failed", e); } catch (SecurityException e) { subresult.recordPartialError("Reflection introspection failed", e); } } OperationResult encryptorSubresult = result .createSubresult(ConnectorFactoryIcfImpl.class + ".selfTestGuardedString.encryptor"); try { String plainString = "Scurvy seadog"; byte[] encryptedBytes = encryptor.encrypt(plainString.getBytes()); byte[] decryptedBytes = encryptor.decrypt(encryptedBytes); String decryptedString = new String(decryptedBytes); if (!plainString.equals(decryptedString)) { encryptorSubresult.recordFatalError( "Encryptor roundtrip failed; encrypted=" + plainString + ", decrypted=" + decryptedString); } else { encryptorSubresult.recordSuccess(); } } catch (Throwable e) { LOGGER.error("Encryptor operation error: {}", e.getMessage(), e); encryptorSubresult.recordFatalError("Encryptor opeation error: " + e.getMessage(), e); } final OperationResult guardedStringSubresult = result .createSubresult(ConnectorFactoryIcfImpl.class + ".selfTestGuardedString.guardedString"); // try to encrypt and decrypt GuardedString try { final String origString = "Shiver me timbers"; // This should encrypt it GuardedString guardedString = new GuardedString(origString.toCharArray()); // and this should decrypt it guardedString.access(new GuardedString.Accessor() { @Override public void access(char[] decryptedChars) { if (!(new String(decryptedChars)).equals(origString)) { guardedStringSubresult.recordFatalError("GuardeString roundtrip failed; encrypted=" + origString + ", decrypted=" + (new String(decryptedChars))); } } }); guardedStringSubresult.recordSuccessIfUnknown(); } catch (Throwable e) { LOGGER.error("GuardedString operation error: {}", e.getMessage(), e); guardedStringSubresult.recordFatalError("GuardedString opeation error: " + e.getMessage(), e); } result.computeStatus(); } static Class<? extends APIOperation> resolveApiOpClass(String opName) { return apiOpMap.get(opName); } static { apiOpMap.put("create", CreateApiOp.class); apiOpMap.put("get", GetApiOp.class); apiOpMap.put("update", UpdateApiOp.class); apiOpMap.put("delete", DeleteApiOp.class); apiOpMap.put("test", TestApiOp.class); apiOpMap.put("scriptOnConnector", ScriptOnConnectorApiOp.class); apiOpMap.put("scriptOnResource", ScriptOnResourceApiOp.class); apiOpMap.put("authentication", AuthenticationApiOp.class); apiOpMap.put("search", SearchApiOp.class); apiOpMap.put("validate", ValidateApiOp.class); apiOpMap.put("sync", SyncApiOp.class); apiOpMap.put("schema", SchemaApiOp.class); } @Override public void shutdown() { ConnectorFacadeFactory.getInstance().dispose(); } }