/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== */ package org.identityconnectors.spml; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.logging.Log; import org.identityconnectors.common.script.ScriptExecutor; import org.identityconnectors.common.script.ScriptExecutorFactory; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.exceptions.AlreadyExistsException; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.exceptions.UnknownUidException; 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.AttributeInfoBuilder; import org.identityconnectors.framework.common.objects.AttributeInfoUtil; import org.identityconnectors.framework.common.objects.AttributeUtil; import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder; 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.ObjectClassInfoBuilder; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.OperationalAttributeInfos; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.ResultsHandler; import org.identityconnectors.framework.common.objects.Schema; import org.identityconnectors.framework.common.objects.SchemaBuilder; import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.framework.common.objects.filter.EqualsFilter; import org.identityconnectors.framework.common.objects.filter.FilterTranslator; import org.identityconnectors.framework.spi.Configuration; import org.identityconnectors.framework.spi.ConnectorClass; import org.identityconnectors.framework.spi.PoolableConnector; import org.identityconnectors.framework.spi.operations.CreateOp; import org.identityconnectors.framework.spi.operations.DeleteOp; import org.identityconnectors.framework.spi.operations.ResolveUsernameOp; import org.identityconnectors.framework.spi.operations.SchemaOp; import org.identityconnectors.framework.spi.operations.SearchOp; import org.identityconnectors.framework.spi.operations.TestOp; import org.identityconnectors.framework.spi.operations.UpdateOp; import org.openspml.v2.msg.OpenContentElement; import org.openspml.v2.msg.pass.ExpirePasswordRequest; import org.openspml.v2.msg.pass.ExpirePasswordResponse; import org.openspml.v2.msg.pass.SetPasswordRequest; import org.openspml.v2.msg.pass.SetPasswordResponse; import org.openspml.v2.msg.spml.AddRequest; import org.openspml.v2.msg.spml.AddResponse; import org.openspml.v2.msg.spml.Capability; import org.openspml.v2.msg.spml.DeleteRequest; import org.openspml.v2.msg.spml.DeleteResponse; import org.openspml.v2.msg.spml.ErrorCode; import org.openspml.v2.msg.spml.ExecutionMode; import org.openspml.v2.msg.spml.Extensible; import org.openspml.v2.msg.spml.ListTargetsRequest; import org.openspml.v2.msg.spml.ListTargetsResponse; import org.openspml.v2.msg.spml.LookupRequest; import org.openspml.v2.msg.spml.LookupResponse; import org.openspml.v2.msg.spml.Modification; import org.openspml.v2.msg.spml.ModificationMode; import org.openspml.v2.msg.spml.ModifyRequest; import org.openspml.v2.msg.spml.ModifyResponse; import org.openspml.v2.msg.spml.PSO; import org.openspml.v2.msg.spml.PSOIdentifier; import org.openspml.v2.msg.spml.Response; import org.openspml.v2.msg.spml.ReturnData; import org.openspml.v2.msg.spml.StatusCode; import org.openspml.v2.msg.spml.Target; import org.openspml.v2.msg.spmlsearch.CloseIteratorRequest; import org.openspml.v2.msg.spmlsearch.IterateRequest; import org.openspml.v2.msg.spmlsearch.IterateResponse; import org.openspml.v2.msg.spmlsearch.Query; import org.openspml.v2.msg.spmlsearch.ResultsIterator; import org.openspml.v2.msg.spmlsearch.Scope; import org.openspml.v2.msg.spmlsearch.SearchRequest; import org.openspml.v2.msg.spmlsearch.SearchResponse; import org.openspml.v2.msg.spmlsuspend.ActiveRequest; import org.openspml.v2.msg.spmlsuspend.ActiveResponse; import org.openspml.v2.msg.spmlsuspend.ResumeRequest; import org.openspml.v2.msg.spmlsuspend.SuspendRequest; import org.openspml.v2.profiles.DSMLProfileRegistrar; import org.openspml.v2.profiles.dsml.DSMLAttr; import org.openspml.v2.profiles.dsml.DSMLModification; import org.openspml.v2.profiles.dsml.DSMLValue; import org.openspml.v2.profiles.dsml.EqualityMatch; import org.openspml.v2.profiles.dsml.FilterItem; import org.openspml.v2.profiles.spmldsml.AttributeDefinitionReference; import org.openspml.v2.profiles.spmldsml.AttributeDefinitionReferences; import org.openspml.v2.profiles.spmldsml.DSMLSchema; import org.openspml.v2.profiles.spmldsml.ObjectClassDefinition; import org.openspml.v2.util.Spml2Exception; import org.openspml.v2.util.Spml2ExceptionWithResponse; import org.openspml.v2.util.xml.ObjectFactory; /** * A Connector to a SPML 2.0 Server. */ @ConnectorClass(displayNameKey = "SPMLConnector", configurationClass = SpmlConfiguration.class) public class SpmlConnector implements PoolableConnector, CreateOp, ResolveUsernameOp, DeleteOp, SearchOp<FilterItem>, UpdateOp, SchemaOp, TestOp { private static final Log LOG = Log.getLog(SpmlConnector.class); private ScriptExecutorFactory scriptExecutorFactory; private static final ObjectFactory.ProfileRegistrar DSML_PROFILE_REGISTRAR = new DSMLProfileRegistrar(); public static final String PSOID = "psoID"; protected SpmlConnection connection; protected SpmlConfiguration configuration; private ScriptExecutor mapAttributeExecutor; private ScriptExecutor mapSetNameExecutor; private ScriptExecutor scriptExecutor; private Map<String, String> objectClassMap; private Map<String, String> targetMap; private Map<String, String> nameAttributeMap; private Schema schema; private Map<String, Map<String, AttributeInfo>> oci; public SpmlConnector() { ObjectFactory.getInstance().register(DSML_PROFILE_REGISTRAR); } /** * {@inheritDoc} */ public void dispose() { if (connection != null) { connection.dispose(); connection = null; } } /** * {@inheritDoc} */ public Configuration getConfiguration() { return configuration; } public void checkAlive() { connection.test(); } /** * {@inheritDoc} */ public void init(Configuration config) { configuration = (SpmlConfiguration) config; connection = SpmlConnectionFactory.newConnection(configuration); scriptExecutorFactory = ScriptExecutorFactory.newInstance(configuration.getScriptingLanguage()); String mapAttributeCommand = configuration.getMapAttributeCommand(); String mapSetNameCommand = configuration.getMapSetNameCommand(); String schemaCommand = configuration.getSchemaCommand(); try { if (mapAttributeCommand != null && mapAttributeCommand.length() > 0) { mapAttributeExecutor = scriptExecutorFactory.newScriptExecutor(getClass().getClassLoader(), mapAttributeCommand, true); } } catch (Exception e) { throw new ConnectorException(configuration .getMessage(SpmlMessages.MAPATTRIBUTE_SCRIPT_ERROR), e); } try { if (mapSetNameCommand != null && mapSetNameCommand.length() > 0) { mapSetNameExecutor = scriptExecutorFactory.newScriptExecutor(getClass().getClassLoader(), mapSetNameCommand, true); } } catch (Exception e) { throw new ConnectorException(configuration .getMessage(SpmlMessages.MAPSETNAME_SCRIPT_ERROR), e); } try { if (schemaCommand != null && schemaCommand.length() > 0) { scriptExecutor = scriptExecutorFactory.newScriptExecutor(getClass().getClassLoader(), schemaCommand, true); } } catch (Exception e) { throw new ConnectorException(configuration .getMessage(SpmlMessages.MAPSCHEMA_SCRIPT_ERROR), e); } objectClassMap = CollectionUtil.newCaseInsensitiveMap(); targetMap = CollectionUtil.newCaseInsensitiveMap(); nameAttributeMap = CollectionUtil.newCaseInsensitiveMap(); if (configuration.getObjectClassNames() != null) { String[] objectClassNames = configuration.getObjectClassNames(); String[] spmlClassNames = configuration.getSpmlClassNames(); String[] targetNames = configuration.getTargetNames(); String[] nameAttributes = configuration.getNameAttributes(); for (int i = 0; i < objectClassNames.length; i++) { objectClassMap.put(objectClassNames[i], spmlClassNames[i]); targetMap.put(objectClassNames[i], targetNames[i]); nameAttributeMap.put(objectClassNames[i], nameAttributes[i]); } } } public Uid resolveUsername(ObjectClass objectClass, String username, OperationOptions options) { if (!objectClass.is(ObjectClass.ACCOUNT_NAME)) { throw new IllegalArgumentException(configuration.getMessage( SpmlMessages.UNSUPPORTED_OBJECTCLASS, objectClass.getObjectClassValue())); } LocalHandler handler = new LocalHandler(); List<FilterItem> query = createFilterTranslator(objectClass, options).translate( new EqualsFilter(AttributeBuilder.build(Name.NAME, username))); executeQuery(ObjectClass.ACCOUNT, query.get(0), handler, options); if (!handler.iterator().hasNext()) { throw new UnknownUidException(); } return handler.iterator().next().getUid(); } /** * {@inheritDoc} */ public Uid create(ObjectClass objectClass, Set<Attribute> attributes, OperationOptions options) { try { Map<String, Attribute> attrMap = new HashMap<String, Attribute>(AttributeUtil.toMap(attributes)); // TODO: need to discuss how to handle group membership // (there may be nothing here, other than remapping name, // which is handled by scripts, but want to be sure). AddRequest request = new AddRequest(); Name name = AttributeUtil.getNameFromAttributes(attributes); LOG.info("create(''{0}'')", name.getNameValue()); request.setTargetId(getTargetForObjectClass(objectClass)); request.setRequestID(objectClassAsString(objectClass.getObjectClassValue()) + ":" + name.getNameValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); // If we are enabling/disabling, that is a separate request // Attribute enable = attrMap.remove(OperationalAttributes.ENABLE_NAME); Attribute disableDate = attrMap.remove(OperationalAttributes.DISABLE_DATE_NAME); Attribute enableDate = attrMap.remove(OperationalAttributes.ENABLE_DATE_NAME); // If we are expiring password, that is a separate request // Attribute expirePassword = attrMap.remove(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME); request.setData(getCreateAttributes(objectClass, attrMap)); AddResponse response = (AddResponse) connection.send(request); Uid uid = null; if (response.getStatus().equals(StatusCode.SUCCESS)) { uid = new Uid(response.getPso().getPsoID().getID()); } else { throw new ConnectorException(asString(response.getErrorMessages())); } processEnable(objectClass, uid, enable, disableDate, enableDate); processExpirePassword(objectClass, uid, expirePassword); return uid; } catch (Spml2ExceptionWithResponse e) { LOG.error(e, "create failed:''{0}''", e.getResponse().getError()); if (e.getResponse().getError() == ErrorCode.ALREADY_EXISTS) { throw new AlreadyExistsException(); } else { throw ConnectorException.wrap(e); } } catch (Exception e) { LOG.error(e, "create failed"); throw ConnectorException.wrap(e); } } protected String asString(String[] strings) { if (strings.length == 0) { return ""; } StringBuilder buffer = new StringBuilder(); for (String string : strings) { buffer.append("\n" + string); } return buffer.toString().substring(1); } protected Extensible getCreateAttributes(ObjectClass objectClass, Map<String, Attribute> attrMap) throws Exception { Extensible extensible = new Extensible(); for (Attribute attribute : attrMap.values()) { String name = attribute.getName(); // validate that attribute is modifiable in schema // if (oci != null) { Map<String, AttributeInfo> info = oci.get(objectClass.getObjectClassValue()); if (info != null) { AttributeInfo attributeInfo = info.get(name); if (attributeInfo != null && !attributeInfo.isCreateable()) { throw new IllegalArgumentException(configuration.getMessage( SpmlMessages.ILLEGAL_MODIFICATION, name)); } } } extensible.addOpenContentElement(new DSMLAttr(mapSetName(name, objectClass .getObjectClassValue()), asDSMLValueArray(attribute))); } extensible.addOpenContentElement(new DSMLAttr("objectclass", objectClassMap.get(objectClass .getObjectClassValue()))); return extensible; } private DSMLValue[] asDSMLValueArray(Attribute attribute) { List<Object> values = attribute.getValue(); if (values == null) { throw new IllegalArgumentException(configuration.getMessage(SpmlMessages.NULL_VALUE, attribute.getName())); } DSMLValue[] array = new DSMLValue[values.size()]; for (int i = 0; i < values.size(); i++) { Object value = values.get(i); if (value instanceof GuardedString) { GuardedStringAccessor accessor = new GuardedStringAccessor(); ((GuardedString) value).access(accessor); array[i] = new DSMLValue(new String(accessor.getArray())); accessor.clear(); } else { if (value == null) { throw new IllegalArgumentException(configuration.getMessage( SpmlMessages.NULL_VALUE, attribute.getName())); } array[i] = new DSMLValue(value.toString()); } } return array; } private List<Object> asValueList(DSMLValue[] values) { List<Object> list = new LinkedList<Object>(); for (DSMLValue value : values) { list.add(value.getValue()); } return list; } /** * {@inheritDoc} */ public void delete(ObjectClass objectClass, Uid uid, OperationOptions options) { try { DeleteRequest request = new DeleteRequest(); PSOIdentifier pso = new PSOIdentifier(); LOG.info("delete(''{0}'')", uid.getUidValue()); pso.setID(uid.getUidValue()); pso.setTargetID(getTargetForObjectClass(objectClass)); request.setPsoID(pso); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); DeleteResponse response = (DeleteResponse) connection.send(request); if (response.getStatus().equals(StatusCode.SUCCESS)) { return; } else { throw new ConnectorException(asString(response.getErrorMessages())); } } catch (Spml2ExceptionWithResponse e) { LOG.error(e, "delete failed:''{0}''", e.getResponse().getError()); throw exceptionForId(e.getResponse()); } catch (Exception e) { LOG.error(e, "delete failed"); throw ConnectorException.wrap(e); } } /** * {@inheritDoc} */ public FilterTranslator<FilterItem> createFilterTranslator(ObjectClass oclass, OperationOptions options) { return new SpmlFilterTranslator(configuration, connection); } /** * {@inheritDoc} */ public void executeQuery(ObjectClass objectClass, FilterItem query, ResultsHandler handler, OperationOptions options) { try { Set<String> attributesToGet = null; if (options != null && options.getAttributesToGet() != null) { attributesToGet = CollectionUtil.newReadOnlySet(options.getAttributesToGet()); } if (query instanceof EqualityMatch) { EqualityMatch equalityMatch = (EqualityMatch) query; String name = equalityMatch.getName(); String value = equalityMatch.getValue().getValue(); // We are filtering by user name // if (name.equals(PSOID) && !value.equals("*")) { try { ConnectorObject object = get(new Uid(value), objectClass, getTargetForObjectClass(objectClass), attributesToGet); handler.handle(object); } catch (UnknownUidException e) { // Ignore } return; } } SearchRequest request = new SearchRequest(); Query spmlQuery = new Query(); spmlQuery.setScope(Scope.ONELEVEL); if (query != null) { spmlQuery.addQueryClause(new org.openspml.v2.profiles.dsml.Filter(query)); } request.setQuery(spmlQuery); request.setReturnData(ReturnData.EVERYTHING); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LOG.info("search(''{0}'')", spmlQuery); SearchResponse response = (SearchResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { throw new ConnectorException(asString(response.getErrorMessages())); } PSO[] psos = response.getPSOs(); for (PSO pso : psos) { LOG.info("search returned ''{0}'' directly", pso.getPsoID().getID()); boolean continueQuery = handler.handle(buildConnectorObject(pso, objectClass, attributesToGet)); if (!continueQuery) { closeIterator(response.getIterator()); } } ResultsIterator iterator = response.getIterator(); while (iterator != null) { IterateRequest iterRequest = new IterateRequest(); iterRequest.setIterator(iterator); iterRequest.setExecutionMode(ExecutionMode.SYNCHRONOUS); IterateResponse iterResponse = (IterateResponse) connection.send(iterRequest); if (!iterResponse.getStatus().equals(StatusCode.SUCCESS)) { throw new ConnectorException(asString(iterResponse.getErrorMessages())); } psos = iterResponse.getPSOs(); for (PSO pso : psos) { LOG.info("search iterator returned ''{0}''", pso.getPsoID().getID()); boolean continueQuery = handler.handle(buildConnectorObject(pso, objectClass, attributesToGet)); if (!continueQuery) { closeIterator(iterator); } } iterator = iterResponse.getIterator(); } } catch (Exception e) { LOG.error(e, "searchRequest failed"); throw ConnectorException.wrap(e); } } SpmlConnection getConnection() { return connection; } private String getTargetForObjectClass(ObjectClass objectClass) { String key = objectClass.getObjectClassValue(); if (targetMap.containsKey(key)) { return targetMap.get(key); } else { throw new ConnectorException(configuration.getMessage( SpmlMessages.UNSUPPORTED_OBJECTCLASS, objectClass)); } } private void closeIterator(ResultsIterator iterator) throws Spml2ExceptionWithResponse, Spml2Exception { CloseIteratorRequest ciRequest = new CloseIteratorRequest(); ciRequest.setIterator(iterator); ciRequest.setExecutionMode(ExecutionMode.SYNCHRONOUS); connection.send(ciRequest); } private ConnectorObject get(Uid uid, ObjectClass objectClass, String targetId, Set<String> attributesToGet) { try { LookupRequest request = new LookupRequest(); PSOIdentifier psoId = new PSOIdentifier(); psoId.setTargetID(targetId); psoId.setID(uid.getUidValue()); LOG.info("get(''{0}'')", uid.getUidValue()); request.setPsoID(psoId); request.setRequestID(uid.getUidValue()); request.setReturnData(ReturnData.EVERYTHING); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LookupResponse response = (LookupResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { throw exceptionForId(response); } // Get Attributes from PSO // PSO pso = response.getPso(); return buildConnectorObject(pso, objectClass, attributesToGet); } catch (Spml2ExceptionWithResponse e) { LOG.error(e, "get failed:''{0}''", e.getResponse().getError()); throw exceptionForId(e.getResponse()); } catch (Exception e) { LOG.error(e, "get failed"); throw ConnectorException.wrap(e); } } private Attribute getActiveStatus(Uid uid, String targetId) { try { ActiveRequest request = new ActiveRequest(); PSOIdentifier psoId = new PSOIdentifier(); psoId.setTargetID(targetId); psoId.setID(uid.getUidValue()); LOG.info("getActiveStatus(''{0}'')", uid.getUidValue()); request.setPsoID(psoId); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); ActiveResponse response = (ActiveResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { throw exceptionForId(response); } return AttributeBuilder.build(OperationalAttributes.ENABLE_NAME, Boolean .valueOf(response.getActive())); } catch (Spml2ExceptionWithResponse e) { LOG.error(e, "ActiveRequest failed:''{0}''", e.getResponse().getError()); throw exceptionForId(e.getResponse()); } catch (Exception e) { LOG.error(e, "ActiveRequest failed"); throw ConnectorException.wrap(e); } } private ConnectorObject buildConnectorObject(PSO pso, ObjectClass objectClass, Set<String> attributesToGet) throws Exception { ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); Uid uid = new Uid(pso.getPsoID().getID()); builder.setUid(uid); OpenContentElement[] psoElements = pso.getData().getOpenContentElements(); String name = null; for (OpenContentElement element : psoElements) { if (element instanceof DSMLAttr) { DSMLAttr attr = (DSMLAttr) element; String attrName = attr.getName(); if (attrName.equals(nameAttributeMap.get(objectClass.getObjectClassValue()))) { name = attr.getValues()[0].getValue(); } if (attributesToGet == null || attrName.equals(Name.NAME) || attributesToGet.contains(attrName)) { builder.addAttribute(mapAttribute(AttributeBuilder.build(attrName, asValueList(attr.getValues())), objectClass.getObjectClassValue())); } } } builder.setObjectClass(objectClass); boolean getEnable = false; if (attributesToGet != null) { getEnable = attributesToGet.contains(OperationalAttributes.ENABLE_NAME); } if (!getEnable && oci != null) { Map<String, AttributeInfo> infos = oci.get(objectClass.getObjectClassValue()); if (infos != null) { AttributeInfo enableInfo = infos.get(OperationalAttributes.ENABLE_NAME); if (enableInfo != null) { getEnable = enableInfo.isReturnedByDefault(); } } } if (getEnable) { builder.addAttribute(getActiveStatus(uid, getTargetforObjectClass(objectClass))); } builder.setName(name); return builder.build(); } private String getTargetforObjectClass(ObjectClass objectClass) { String[] classNames = configuration.getObjectClassNames(); String[] targetNames = configuration.getTargetNames(); for (int i = 0; i < classNames.length; i++) { if (objectClass.is(classNames[i])) { return targetNames[i]; } } return null; } private PSOIdentifier getPsoIdentifier(Uid uid, ObjectClass objectClass) { PSOIdentifier pso = new PSOIdentifier(); pso.setID(uid.getUidValue()); pso.setTargetID(getTargetForObjectClass(objectClass)); return pso; } public Uid update(ObjectClass obj, Uid uid, Set<Attribute> attrs, OperationOptions options) { return update(obj, AttributeUtil.addUid(attrs, uid), options); } /** * {@inheritDoc} */ Uid update(ObjectClass objectClass, Set<Attribute> attributes, OperationOptions options) { try { Map<String, Attribute> attrMap = new HashMap<String, Attribute>(AttributeUtil.toMap(attributes)); Uid uid = (Uid) attrMap.remove(Uid.NAME); // If we are enabling/disabling, that is a separate request // Attribute enable = attrMap.remove(OperationalAttributes.ENABLE_NAME); Attribute disableDate = attrMap.remove(OperationalAttributes.DISABLE_DATE_NAME); Attribute enableDate = attrMap.remove(OperationalAttributes.ENABLE_DATE_NAME); processEnable(objectClass, uid, enable, disableDate, enableDate); // If we are changing password, that is a separate request // Attribute password = attrMap.remove(OperationalAttributes.PASSWORD_NAME); processPassword(objectClass, uid, password); // If we are expiring password, that is a separate request // Attribute expirePassword = attrMap.remove(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME); processExpirePassword(objectClass, uid, expirePassword); // Remaining attributes are handled here // if (attrMap.size() > 0) { ModifyRequest request = new ModifyRequest(); setModifications(request, objectClass, attrMap); request.setPsoID(getPsoIdentifier(uid, objectClass)); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); request.setReturnData(ReturnData.EVERYTHING); LOG.info("update(''{0}''", uid.getUidValue()); ModifyResponse response = (ModifyResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { LOG.error("update failed:''{0}''", response.getError()); throw exceptionForId(response); } } return uid; } catch (Spml2ExceptionWithResponse e) { LOG.error(e, "update failed:''{0}''", e.getResponse().getError()); throw exceptionForId(e.getResponse()); } catch (Exception e) { LOG.error(e, "update failed"); throw ConnectorException.wrap(e); } } private void processPassword(ObjectClass objectClass, Uid uid, Attribute password) throws Spml2ExceptionWithResponse, Spml2Exception { if (password != null) { SetPasswordRequest request = new SetPasswordRequest(); GuardedString passwordGS = AttributeUtil.getGuardedStringValue(password); GuardedStringAccessor accessor = new GuardedStringAccessor(); passwordGS.access(accessor); String passwordString = new String(accessor.getArray()); accessor.clear(); request.setPassword(passwordString); request.setPsoID(getPsoIdentifier(uid, objectClass)); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LOG.info("change password(''{0}'')", uid.getUidValue()); SetPasswordResponse response = (SetPasswordResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { LOG.error("change password failed:''{0}''", response.getError()); throw exceptionForId(response); } } } private void processExpirePassword(ObjectClass objectClass, Uid uid, Attribute expirePassword) throws Spml2ExceptionWithResponse, Spml2Exception { if (expirePassword != null) { ExpirePasswordRequest request = new ExpirePasswordRequest(); request.setRemainingLogins(0); request.setPsoID(getPsoIdentifier(uid, objectClass)); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LOG.info("expire password(''{0}''", uid.getUidValue()); ExpirePasswordResponse response = (ExpirePasswordResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { LOG.error("expire password failed:''{0}''", response.getError()); throw exceptionForId(response); } } } private void processEnable(ObjectClass objectClass, Uid uid, Attribute enable, Attribute disableDate, Attribute enableDate) throws Spml2ExceptionWithResponse, Spml2Exception { if (enable != null) { boolean isEnable = AttributeUtil.getBooleanValue(enable); if (isEnable) { ResumeRequest request = new ResumeRequest(); Long date = enableDate != null ? AttributeUtil.getLongValue(enableDate) : null; if (date != null) { // Date must be specified as UTC date with no Time Zone // component // Date effectiveDate = new Date(date); String dateString = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS").format(effectiveDate); request.setEffectiveDate(dateString); } request.setPsoID(getPsoIdentifier(uid, objectClass)); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LOG.info("enable(''{0}'')", uid.getUidValue()); Response response = connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { LOG.error("enable failed:''{0}''", response.getError()); throw exceptionForId(response); } } else { SuspendRequest request = new SuspendRequest(); Long date = disableDate != null ? AttributeUtil.getLongValue(disableDate) : null; if (date != null) { // Date must be specified as UTC date with no Time Zone // component // Date effectiveDate = new Date(date); String dateString = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS").format(effectiveDate); request.setEffectiveDate(dateString); } request.setPsoID(getPsoIdentifier(uid, objectClass)); request.setRequestID(uid.getUidValue()); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LOG.info("disable(''{0}'')", uid.getUidValue()); Response response = connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { LOG.error("disable failed:''{0}''", response.getError()); throw exceptionForId(response); } } } } protected void setModifications(ModifyRequest modifyRequest, ObjectClass objectClass, Map<String, Attribute> attributes) throws Exception { for (Attribute attribute : attributes.values()) { String name = attribute.getName(); // validate that attribute is modifiable in schema // if (oci != null) { Map<String, AttributeInfo> info = oci.get(objectClass.getObjectClassValue()); if (info != null) { AttributeInfo attributeInfo = info.get(name); if (attributeInfo != null && !attributeInfo.isUpdateable()) { throw new IllegalArgumentException(configuration.getMessage( SpmlMessages.ILLEGAL_MODIFICATION, name)); } } } Modification modification = new Modification(); modification .addOpenContentElement(new DSMLModification(mapSetName(name, objectClass .getObjectClassValue()), asDSMLValueArray(attribute), ModificationMode.REPLACE)); modifyRequest.addModification(modification); } } /** * {@inheritDoc} */ public void test() { SpmlConnection connection = SpmlConnectionFactory.newConnection(configuration); try { connection.test(); } finally { connection.dispose(); } } /** * {@inheritDoc} */ public Schema schema() { final SchemaBuilder schemaBuilder = new SchemaBuilder(getClass()); try { ListTargetsRequest request = new ListTargetsRequest(); request.setExecutionMode(ExecutionMode.SYNCHRONOUS); LOG.info("listTargets"); ListTargetsResponse response = (ListTargetsResponse) connection.send(request); if (!response.getStatus().equals(StatusCode.SUCCESS)) { LOG.error("listTargets failed:''{0}''", response.getError()); throw new ConnectorException(asString(response.getErrorMessages())); } Target[] targets = response.getTargets(); String[] spmlClassNames = configuration.getSpmlClassNames(); String[] objectClassNames = configuration.getObjectClassNames(); int length = spmlClassNames == null ? 0 : spmlClassNames.length; for (Target target : targets) { org.openspml.v2.msg.spml.Schema[] schemas = target.getSchemas(); for (org.openspml.v2.msg.spml.Schema schema : schemas) { for (OpenContentElement element : schema.getOpenContentElements()) { if (element instanceof DSMLSchema) { DSMLSchema dsmlSchema = (DSMLSchema) element; for (ObjectClassDefinition ocd : dsmlSchema.getObjectClassDefinitions()) { for (int i = 0; i < length; i++) { if (spmlClassNames[i].equals(ocd.getName())) { Set<AttributeInfo> attributes = new HashSet<AttributeInfo>(); AttributeDefinitionReferences refs = ocd.getMemberAttributes(); Capability[] capabilities = target.getCapabilities().getCapabilities(); fillInSchemaForObjectClass(schemaBuilder, objectClassNames[i], refs, attributes, capabilities); } } } } } } } } catch (Spml2ExceptionWithResponse e) { LOG.error(e, "update failed:''{0}''", e.getResponse().getError()); throw new ConnectorException(asString(e.getResponse().getErrorMessages())); } catch (Exception e) { LOG.error(e, "listTargets failed"); throw ConnectorException.wrap(e); } schema = schemaBuilder.build(); oci = CollectionUtil.newCaseInsensitiveMap(); for (ObjectClassInfo info : schema.getObjectClassInfo()) { oci.put(info.getType(), AttributeInfoUtil.toMap(info.getAttributeInfo())); } return schema; } private void fillInSchemaForObjectClass(final SchemaBuilder schemaBuilder, String objectClass, AttributeDefinitionReferences refs, Set<AttributeInfo> attributes, Capability[] capabilities) throws Exception { for (AttributeDefinitionReference ref : refs.getAttributeDefinitionReferences()) { boolean required = false; if (ref.getRequired() != null) { required = ref.getRequired(); } attributes.add(new AttributeInfoBuilder(ref.getName()).setRequired(required).build()); } boolean searchFound = false; for (Capability capability : capabilities) { if (capability.getNamespaceURI().toASCIIString().equals( "urn:oasis:names:tc:SPML:2:0:password")) { attributes.add(OperationalAttributeInfos.PASSWORD); } if (capability.getNamespaceURI().toASCIIString().equals( "urn:oasis:names:tc:SPML:2:0:suspend")) { attributes.add(OperationalAttributeInfos.ENABLE); } if (capability.getNamespaceURI().toASCIIString().equals( "urn:oasis:names:tc:SPML:2:0:search")) { searchFound = true; } } updateSchema(objectClass, attributes); ObjectClassInfoBuilder bld = new ObjectClassInfoBuilder(); bld.setType(objectClass); bld.addAllAttributeInfo(attributes); ObjectClassInfo objectClassInfo = bld.build(); schemaBuilder.defineObjectClass(objectClassInfo); if (!searchFound) { schemaBuilder.removeSupportedObjectClass(SearchOp.class, objectClassInfo); } } private String mapSetName(String name, String objectClass) throws Exception { try { if (mapSetNameExecutor != null) { Map<String, Object> arguments = new HashMap<String, Object>(); arguments.put("name", name); arguments.put("objectClass", objectClass); arguments.put("configuration", configuration); arguments.put("memory", connection.getMemory()); return (String) mapSetNameExecutor.execute(arguments); } return name; } catch (Exception e) { throw new ConnectorException(configuration .getMessage(SpmlMessages.MAPSETNAME_SCRIPT_ERROR), e); } } private void updateSchema(String objectClass, Set<AttributeInfo> attributeInfos) throws Exception { try { if (scriptExecutor != null) { Map<String, Object> arguments = new HashMap<String, Object>(); arguments.put("objectClass", objectClass); arguments.put("attributeInfos", attributeInfos); arguments.put("memory", connection.getMemory()); scriptExecutor.execute(arguments); } } catch (Exception e) { throw new ConnectorException(configuration .getMessage(SpmlMessages.MAPSCHEMA_SCRIPT_ERROR), e); } } private Attribute mapAttribute(Attribute attribute, String objectClass) throws Exception { try { if (mapAttributeExecutor != null) { Map<String, Object> arguments = new HashMap<String, Object>(); arguments.put("attribute", attribute); arguments.put("objectClass", objectClass); arguments.put("configuration", configuration); arguments.put("memory", connection.getMemory()); return (Attribute) mapAttributeExecutor.execute(arguments); } return attribute; } catch (Exception e) { throw new ConnectorException(configuration .getMessage(SpmlMessages.MAPATTRIBUTE_SCRIPT_ERROR), e); } } private ConnectorException exceptionForId(Response response) { if (response.getError() == ErrorCode.NO_SUCH_IDENTIFIER) { return new UnknownUidException(); } else { return new ConnectorException(asString(response.getErrorMessages())); } } private String objectClassAsString(String objectClass) { if (objectClassMap.containsKey(objectClass)) { return objectClassMap.get(objectClass); } else { throw new ConnectorException(configuration.getMessage( SpmlMessages.UNSUPPORTED_OBJECTCLASS, objectClass)); } } public static class LocalHandler implements ResultsHandler, Iterable<ConnectorObject> { private List<ConnectorObject> objects = new LinkedList<ConnectorObject>(); public boolean handle(ConnectorObject object) { objects.add(object); return true; } public Iterator<ConnectorObject> iterator() { return objects.iterator(); } } }