diff --git a/java/archetype/src/main/resources/archetype-resources/src/main/java/SampleConnector.java b/java/archetype/src/main/resources/archetype-resources/src/main/java/SampleConnector.java index fdc8f229..5462c34c 100644 --- a/java/archetype/src/main/resources/archetype-resources/src/main/java/SampleConnector.java +++ b/java/archetype/src/main/resources/archetype-resources/src/main/java/SampleConnector.java @@ -11,6 +11,7 @@ import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp; import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.ObjectClassInfo; import org.identityconnectors.framework.common.objects.OperationOptionInfo; @@ -30,6 +31,7 @@ import org.identityconnectors.framework.spi.operations.DeleteOp; import org.identityconnectors.framework.spi.operations.SchemaOp; import org.identityconnectors.framework.spi.operations.SearchOp; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; import org.identityconnectors.framework.spi.operations.SyncOp; import org.identityconnectors.framework.spi.operations.TestOp; import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp; @@ -42,7 +44,7 @@ @ConnectorClass(configurationClass = SampleConfiguration.class, displayNameKey = "sample.connector.display") public class SampleConnector implements Connector, CreateOp, UpdateOp, UpdateAttributeValuesOp, DeleteOp, - AuthenticateOp, ResolveUsernameApiOp, SchemaOp, SyncOp, TestOp, SearchOp { + AuthenticateOp, ResolveUsernameApiOp, SchemaOp, SyncOp, LiveSyncOp, TestOp, SearchOp { private static final Log LOG = Log.getLog(SampleConnector.class); @@ -146,6 +148,13 @@ public void sync( final OperationOptions options) { } + @Override + public void livesync( + final ObjectClass objectClass, + final LiveSyncResultsHandler handler, + final OperationOptions options) { + } + @Override public SyncToken getLatestSyncToken(final ObjectClass objectClass) { return new SyncToken(null); diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/AbstractConnectorFacade.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/AbstractConnectorFacade.java index 15e1e1a3..05536ee4 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/AbstractConnectorFacade.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/AbstractConnectorFacade.java @@ -21,7 +21,7 @@ * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. * Portions Copyrighted 2014-2018 Evolveum - * Portions Copyrighted 2015-2018 ConnId + * Portions Copyrighted 2015-2024 ConnId */ package org.identityconnectors.framework.impl.api; @@ -130,7 +130,9 @@ public final Schema schema() { * {@inheritDoc} */ @Override - public final Uid create(final ObjectClass objectClass, final Set createAttributes, + public final Uid create( + final ObjectClass objectClass, + final Set createAttributes, final OperationOptions options) { return ((CreateApiOp) getOperationCheckSupported(CreateApiOp.class)). @@ -150,13 +152,14 @@ public final void delete(final ObjectClass objectClass, final Uid uid, final Ope */ @Override public final SearchResult search(final ObjectClass objectClass, final Filter filter, - ResultsHandler handler, final OperationOptions options) { + final ResultsHandler handler, final OperationOptions options) { + ResultsHandler resultsHandler = handler; if (LoggingProxy.isLoggable()) { - handler = new SearchResultsHandlerLoggingProxy(handler, LOG, null); + resultsHandler = new SearchResultsHandlerLoggingProxy(handler, LOG, null); } return ((SearchApiOp) this.getOperationCheckSupported(SearchApiOp.class)). - search(objectClass, filter, handler, options); + search(objectClass, filter, resultsHandler, options); } /** @@ -289,12 +292,25 @@ public final SyncToken getLatestSyncToken(final ObjectClass objectClass) { return ((SyncApiOp) this.getOperationCheckSupported(SyncApiOp.class)).getLatestSyncToken(objectClass); } + /** + * {@inheritDoc} + */ + @Override + public void livesync( + final ObjectClass objectClass, + final LiveSyncResultsHandler handler, + final OperationOptions options) { + + ((LiveSyncApiOp) this.getOperationCheckSupported(LiveSyncApiOp.class)).livesync(objectClass, handler, options); + } + /** * {@inheritDoc} */ @Override public final void testPartialConfiguration() { - ((DiscoverConfigurationApiOp) this.getOperationCheckSupported(DiscoverConfigurationApiOp.class)).testPartialConfiguration(); + ((DiscoverConfigurationApiOp) this.getOperationCheckSupported(DiscoverConfigurationApiOp.class)). + testPartialConfiguration(); } /** @@ -302,7 +318,8 @@ public final void testPartialConfiguration() { */ @Override public final Map discoverConfiguration() { - return ((DiscoverConfigurationApiOp) this.getOperationCheckSupported(DiscoverConfigurationApiOp.class)).discoverConfiguration(); + return ((DiscoverConfigurationApiOp) this.getOperationCheckSupported(DiscoverConfigurationApiOp.class)). + discoverConfiguration(); } private static final String MSG = "Operation ''{0}'' not supported."; @@ -317,7 +334,7 @@ private APIOperation getOperationCheckSupported(final Class... apis) { + private APIOperation getDeltaOperationCheckSupported(final Class... apis) { // check if this operation is supported. for (Class api : apis) { if (configuration.isSupportedOperation(api)) { diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ConnectorFacadeFactoryImpl.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ConnectorFacadeFactoryImpl.java index c65da800..181763a5 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ConnectorFacadeFactoryImpl.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ConnectorFacadeFactoryImpl.java @@ -93,9 +93,8 @@ public ConnectorFacade newInstance(final ConnectorInfo connectorInfo, String con */ @Override public void dispose() { - // Disposal of connector factory means shutdown of all connector pools. - // This is the end. No more connector instances will be created. + // Disposal of connector factory means shutdown of all connector pools. + // This is the end. No more connector instances will be created. ConnectorPoolManager.shutdown(); } - } diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ManagedConnectorFacadeFactoryImpl.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ManagedConnectorFacadeFactoryImpl.java index 2209306c..d9d05299 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ManagedConnectorFacadeFactoryImpl.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/ManagedConnectorFacadeFactoryImpl.java @@ -19,13 +19,12 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== + * Portions Copyrighted 2024 ConnId */ - package org.identityconnectors.framework.impl.api; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; - import org.identityconnectors.common.logging.Log; import org.identityconnectors.framework.api.APIConfiguration; import org.identityconnectors.framework.api.ConnectorFacade; @@ -39,8 +38,7 @@ public class ManagedConnectorFacadeFactoryImpl extends ConnectorFacadeFactoryImp /** * Cache of the various ConnectorFacades. */ - private static final ConcurrentMap CACHE = - new ConcurrentHashMap(); + private static final ConcurrentMap CACHE = new ConcurrentHashMap<>(); /** * {@inheritDoc} @@ -82,7 +80,7 @@ public void dispose() { for (ConnectorFacade facade : CACHE.values()) { if (facade instanceof LocalConnectorFacadeImpl) { try { - ((LocalConnectorFacadeImpl) facade).dispose(); + facade.dispose(); } catch (Exception e) { LOG.warn(e, "Failed to dispose facade: {0}", facade); } @@ -90,5 +88,4 @@ public void dispose() { } CACHE.clear(); } - } diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/LocalConnectorFacadeImpl.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/LocalConnectorFacadeImpl.java index 17970b4d..83625879 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/LocalConnectorFacadeImpl.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/LocalConnectorFacadeImpl.java @@ -20,6 +20,7 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2014 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.impl.api.local; @@ -37,7 +38,6 @@ /** * Implements all the methods of the facade. - *

*/ public class LocalConnectorFacadeImpl extends AbstractConnectorFacade { @@ -47,11 +47,14 @@ public class LocalConnectorFacadeImpl extends AbstractConnectorFacade { /** * Map the API interfaces to their implementation counterparts. */ - private static final Map, Constructor> API_TO_IMPL = - new HashMap, Constructor>(); + private static final Map< + Class, Constructor> API_TO_IMPL = + new HashMap<>(); - private static void addImplementation(final Class inter, + private static void addImplementation( + final Class inter, final Class impl) { + Constructor constructor; try { constructor = impl.getConstructor(ConnectorOperationalContext.class, Connector.class); @@ -75,13 +78,13 @@ private static void addImplementation(final Class inter, addImplementation(ScriptOnConnectorApiOp.class, ScriptOnConnectorImpl.class); addImplementation(ScriptOnResourceApiOp.class, ScriptOnResourceImpl.class); addImplementation(SyncApiOp.class, SyncImpl.class); + addImplementation(LiveSyncApiOp.class, LiveSyncImpl.class); addImplementation(DiscoverConfigurationApiOp.class, DiscoverConfigurationImpl.class); } // ======================================================================= // Fields // ======================================================================= - /** * The connector info */ @@ -95,28 +98,26 @@ private static void addImplementation(final Class inter, /** * Builds up the maps of supported operations and calls. */ - public LocalConnectorFacadeImpl(final LocalConnectorInfoImpl connectorInfo, + public LocalConnectorFacadeImpl( + final LocalConnectorInfoImpl connectorInfo, final APIConfigurationImpl apiConfiguration) { + super(apiConfiguration); this.connectorInfo = connectorInfo; - if (connectorInfo.isConfigurationStateless() - && !connectorInfo.isConnectorPoolingSupported()) { + if (connectorInfo.isConfigurationStateless() && !connectorInfo.isConnectorPoolingSupported()) { operationalContext = null; } else { - operationalContext = - new ConnectorOperationalContext(connectorInfo, getAPIConfiguration()); + operationalContext = new ConnectorOperationalContext(connectorInfo, getAPIConfiguration()); } } public LocalConnectorFacadeImpl(final LocalConnectorInfoImpl connectorInfo, String configuration) { super(configuration, connectorInfo); this.connectorInfo = connectorInfo; - if (connectorInfo.isConfigurationStateless() - && !connectorInfo.isConnectorPoolingSupported()) { + if (connectorInfo.isConfigurationStateless() && !connectorInfo.isConnectorPoolingSupported()) { operationalContext = null; } else { - operationalContext = - new ConnectorOperationalContext(connectorInfo, getAPIConfiguration()); + operationalContext = new ConnectorOperationalContext(connectorInfo, getAPIConfiguration()); } } @@ -137,22 +138,16 @@ protected ConnectorOperationalContext getOperationalContext() { // ======================================================================= // ConnectorFacade Interface // ======================================================================= - @Override protected APIOperation getOperationImplementation(final Class api) { - APIOperation proxy; - // first create the inner proxy - this is the proxy that obtaining - // a connector from the pool, etc - // NOTE: we want to skip this part of the proxy for - // validate op, but we will want the timeout proxy + // first create the inner proxy - this is the proxy that obtaining a connector from the pool, etc + // NOTE: we want to skip this part of the proxy for validate op, but we will want the timeout proxy if (api == ValidateApiOp.class) { - final OperationalContext context = - new OperationalContext(connectorInfo, getAPIConfiguration()); + final OperationalContext context = new OperationalContext(connectorInfo, getAPIConfiguration()); proxy = new ValidateImpl(context); } else if (api == GetApiOp.class) { - final Constructor constructor = - API_TO_IMPL.get(SearchApiOp.class); + final Constructor constructor = API_TO_IMPL.get(SearchApiOp.class); final ConnectorAPIOperationRunnerProxy handler = new ConnectorAPIOperationRunnerProxy(getOperationalContext(), constructor); proxy = new GetImpl((SearchApiOp) newAPIOperationProxy(SearchApiOp.class, handler)); @@ -164,9 +159,8 @@ protected APIOperation getOperationImplementation(final Class 0 && hdlCfg.isEnableAttributesToGetSearchResultsHandler()) { + handler = new AttributesToGetLiveSyncResultsHandler(handler, attrsToGet); + } + // chain a normalizing results handler + if (getConnector() instanceof AttributeNormalizer && hdlCfg.isEnableNormalizingResultsHandler()) { + handler = new NormalizingLiveSyncResultsHandler(handler, getNormalizer(objectClass)); + } + + SpiOperationLoggingUtil.logOpEntry( + OP_LOG, getOperationalContext(), LiveSyncOp.class, "livesync", objectClass, options); + + try { + ((LiveSyncOp) getConnector()).livesync(objectClass, handler, options); + } catch (RuntimeException e) { + SpiOperationLoggingUtil.logOpException(OP_LOG, getOperationalContext(), LiveSyncOp.class, "livesync", e); + throw e; + } + + SpiOperationLoggingUtil.logOpExit(OP_LOG, getOperationalContext(), LiveSyncOp.class, "livesync"); + } + + /** + * Simple handler to reduce the attributes to only the set of attribute to get. + */ + public static class AttributesToGetLiveSyncResultsHandler + extends AttributesToGetResultsHandler implements LiveSyncResultsHandler { + + private final LiveSyncResultsHandler handler; + + public AttributesToGetLiveSyncResultsHandler(final LiveSyncResultsHandler handler, String[] attrsToGet) { + super(attrsToGet); + this.handler = handler; + } + + @Override + public boolean handle(final LiveSyncDelta delta) { + LiveSyncDeltaBuilder bld = new LiveSyncDeltaBuilder(delta); + if (delta.getObject() != null) { + bld.setObject(reduceToAttrsToGet(delta.getObject())); + } + return handler.handle(bld.build()); + } + } +} diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingLiveSyncResultsHandler.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingLiveSyncResultsHandler.java new file mode 100644 index 00000000..8122833f --- /dev/null +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingLiveSyncResultsHandler.java @@ -0,0 +1,50 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 ConnId. 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.framework.impl.api.local.operations; + +import org.identityconnectors.common.Assertions; +import org.identityconnectors.framework.common.objects.LiveSyncDelta; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; + +public class NormalizingLiveSyncResultsHandler implements LiveSyncResultsHandler { + + private final LiveSyncResultsHandler target; + + private final ObjectNormalizerFacade normalizer; + + public NormalizingLiveSyncResultsHandler( + final LiveSyncResultsHandler target, + final ObjectNormalizerFacade normalizer) { + + Assertions.nullCheck(target, "target"); + Assertions.nullCheck(normalizer, "normalizer"); + this.target = target; + this.normalizer = normalizer; + } + + @Override + public boolean handle(final LiveSyncDelta delta) { + LiveSyncDelta normalized = normalizer.normalizeLiveSyncDelta(delta); + return target.handle(normalized); + } +} diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingSyncResultsHandler.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingSyncResultsHandler.java index f680aa47..9f2837bb 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingSyncResultsHandler.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/NormalizingSyncResultsHandler.java @@ -20,22 +20,24 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2014 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.impl.api.local.operations; import org.identityconnectors.common.Assertions; import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncResultsHandler; -import org.identityconnectors.framework.common.objects.SyncToken; -import org.identityconnectors.framework.spi.SyncTokenResultsHandler; public class NormalizingSyncResultsHandler implements SyncResultsHandler { private final SyncResultsHandler target; + private final ObjectNormalizerFacade normalizer; - public NormalizingSyncResultsHandler(SyncResultsHandler target, - ObjectNormalizerFacade normalizer) { + public NormalizingSyncResultsHandler( + final SyncResultsHandler target, + final ObjectNormalizerFacade normalizer) { + Assertions.nullCheck(target, "target"); Assertions.nullCheck(normalizer, "normalizer"); this.target = target; @@ -43,7 +45,7 @@ public NormalizingSyncResultsHandler(SyncResultsHandler target, } @Override - public boolean handle(SyncDelta delta) { + public boolean handle(final SyncDelta delta) { SyncDelta normalized = normalizer.normalizeSyncDelta(delta); return target.handle(normalized); } diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java index e5765f3e..a686c299 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java @@ -20,16 +20,19 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.impl.api.local.operations; import java.util.Collections; import java.util.HashSet; +import java.util.Optional; import java.util.Set; - import org.identityconnectors.common.Assertions; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.LiveSyncDelta; +import org.identityconnectors.framework.common.objects.LiveSyncDeltaBuilder; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncDeltaBuilder; @@ -50,10 +53,12 @@ import org.identityconnectors.framework.spi.AttributeNormalizer; public final class ObjectNormalizerFacade { + /** * The (non-null) object class */ private final ObjectClass objectClass; + /** * The (possibly null) attribute normalizer */ @@ -62,12 +67,10 @@ public final class ObjectNormalizerFacade { /** * Create a new ObjectNormalizer. * - * @param objectClass - * The object class - * @param normalizer - * The normalizer. May be null. + * @param objectClass The object class + * @param normalizer The normalizer. May be null. */ - public ObjectNormalizerFacade(final ObjectClass objectClass,final AttributeNormalizer normalizer) { + public ObjectNormalizerFacade(final ObjectClass objectClass, final AttributeNormalizer normalizer) { Assertions.nullCheck(objectClass, "objectClass"); this.objectClass = objectClass; this.normalizer = normalizer; @@ -78,8 +81,7 @@ public ObjectNormalizerFacade(final ObjectClass objectClass,final AttributeNorma * * If no normalizer is specified, returns the original attribute. * - * @param attribute - * The attribute to normalize. + * @param attribute The attribute to normalize. * @return The normalized attribute */ public Attribute normalizeAttribute(Attribute attribute) { @@ -93,18 +95,16 @@ public Attribute normalizeAttribute(Attribute attribute) { } /** - * Returns the normalized set of attributes or null if the original set is - * null. + * Returns the normalized set of attributes or null if the original set is null. * - * @param attributes - * The original attributes. + * @param attributes The original attributes. * @return The normalized attributes or null if the original set is null. */ public Set normalizeAttributes(Set attributes) { if (attributes == null) { return null; } - Set temp = new HashSet(); + Set temp = new HashSet<>(); for (Attribute attribute : attributes) { temp.add(normalizeAttribute(attribute)); } @@ -114,8 +114,7 @@ public Set normalizeAttributes(Set attributes) { /** * Returns the normalized object. * - * @param orig - * The original object + * @param orig The original object * @return The normalized object. */ public ConnectorObject normalizeObject(ConnectorObject orig) { @@ -125,15 +124,24 @@ public ConnectorObject normalizeObject(ConnectorObject orig) { /** * Returns the normalized sync delta. * - * @param delta - * The original delta. + * @param delta The original delta. * @return The normalized delta. */ public SyncDelta normalizeSyncDelta(SyncDelta delta) { SyncDeltaBuilder builder = new SyncDeltaBuilder(delta); - if (delta.getObject() != null) { - builder.setObject(normalizeObject(delta.getObject())); - } + Optional.ofNullable(delta.getObject()).ifPresent(o -> builder.setObject(normalizeObject(o))); + return builder.build(); + } + + /** + * Returns the normalized live sync delta. + * + * @param delta The original delta. + * @return The normalized delta. + */ + public LiveSyncDelta normalizeLiveSyncDelta(LiveSyncDelta delta) { + LiveSyncDeltaBuilder builder = new LiveSyncDeltaBuilder(delta); + Optional.ofNullable(delta.getObject()).ifPresent(o -> builder.setObject(normalizeObject(o))); return builder.build(); } @@ -141,8 +149,7 @@ public SyncDelta normalizeSyncDelta(SyncDelta delta) { * Returns a filter consisting of the original with all attributes * normalized. * - * @param filter - * The original. + * @param filter The original. * @return The normalized filter. */ public Filter normalizeFilter(Filter filter) { @@ -178,15 +185,12 @@ public Filter normalizeFilter(Filter filter) { return new NotFilter(normalizeFilter(notFilter.getFilter())); } else if (filter instanceof AndFilter) { AndFilter andFilter = (AndFilter) filter; - return new AndFilter(normalizeFilter(andFilter.getLeft()), normalizeFilter(andFilter - .getRight())); + return new AndFilter(normalizeFilter(andFilter.getLeft()), normalizeFilter(andFilter.getRight())); } else if (filter instanceof OrFilter) { OrFilter orFilter = (OrFilter) filter; - return new OrFilter(normalizeFilter(orFilter.getLeft()), normalizeFilter(orFilter - .getRight())); + return new OrFilter(normalizeFilter(orFilter.getLeft()), normalizeFilter(orFilter.getRight())); } else { return filter; } } - } diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java index 59aa530a..287f8be7 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java @@ -21,6 +21,7 @@ * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. * Portions Copyrighted 2014-2018 Evolveum + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.impl.api.local.operations; @@ -66,9 +67,9 @@ public SyncToken sync( options = new OperationOptionsBuilder().build(); } - ResultsHandlerConfiguration hdlCfg = - null != getOperationalContext() ? getOperationalContext() - .getResultsHandlerConfiguration() : new ResultsHandlerConfiguration(); + ResultsHandlerConfiguration hdlCfg = null != getOperationalContext() + ? getOperationalContext().getResultsHandlerConfiguration() + : new ResultsHandlerConfiguration(); // add a handler in the chain to remove attributes String[] attrsToGet = options.getAttributesToGet(); @@ -77,8 +78,7 @@ public SyncToken sync( } // chain a normalizing results handler if (getConnector() instanceof AttributeNormalizer && hdlCfg.isEnableNormalizingResultsHandler()) { - handler = - new NormalizingSyncResultsHandler(handler, getNormalizer(objectClass)); + handler = new NormalizingSyncResultsHandler(handler, getNormalizer(objectClass)); } final SyncResultsHandler handlerChain = handler; @@ -148,8 +148,7 @@ public SyncToken getLatestSyncToken(ObjectClass objectClass) { } /** - * Simple handler to reduce the attributes to only the set of attribute to - * get. + * Simple handler to reduce the attributes to only the set of attribute to get. */ public static class AttributesToGetSyncResultsHandler extends AttributesToGetResultsHandler implements SyncResultsHandler { diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteConnectorFacadeImpl.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteConnectorFacadeImpl.java index 2b4a01bf..d491b8b6 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteConnectorFacadeImpl.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteConnectorFacadeImpl.java @@ -21,12 +21,12 @@ * ==================== * Portions Copyrighted 2010-2014 ForgeRock AS. * Portions Copyrighted 2018 Evolveum + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.impl.api.remote; import java.lang.reflect.InvocationHandler; import java.util.HashMap; - import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.common.serializer.SerializerUtil; import org.identityconnectors.framework.impl.api.APIConfigurationImpl; @@ -38,6 +38,13 @@ */ public class RemoteConnectorFacadeImpl extends AbstractConnectorFacade { + private static String generateRemoteConnectorFacadeKey(final APIConfigurationImpl configuration) { + APIConfigurationImpl copy = new APIConfigurationImpl(configuration); + copy.setProducerBufferSize(0); + copy.setTimeoutMap(new HashMap<>()); + return SerializerUtil.serializeBase64Object(copy); + } + final String remoteConnectorFacadeKey; /** @@ -57,13 +64,6 @@ public RemoteConnectorFacadeImpl(final RemoteConnectorInfoImpl connectorInfo, remoteConnectorFacadeKey = generateRemoteConnectorFacadeKey(getAPIConfiguration()); } - private static String generateRemoteConnectorFacadeKey(final APIConfigurationImpl configuration){ - APIConfigurationImpl copy = new APIConfigurationImpl(configuration); - copy.setProducerBufferSize(0); - copy.setTimeoutMap(new HashMap, Integer>()); - return SerializerUtil.serializeBase64Object(copy); - } - @Override protected APIOperation getOperationImplementation(final Class api) { // add remote proxy @@ -81,8 +81,8 @@ protected APIOperation getOperationImplementation(final Class HANDLERS = new ArrayList(); + private static final List HANDLERS = new ArrayList<>(); - private static final Map HANDLERS_BY_SERIAL_TYPE = - new HashMap(); + private static final Map HANDLERS_BY_SERIAL_TYPE = new HashMap<>(); // initialize list of handlers static { @@ -46,13 +46,11 @@ public final class ObjectSerializerRegistry { HANDLERS.addAll(FilterHandlers.HANDLERS); HANDLERS.addAll(CommonObjectHandlers.HANDLERS); HANDLERS.addAll(MessageHandlers.HANDLERS); - // object is special - just map the type, but don't actually - // serialize + // object is special - just map the type, but don't actually serialize HANDLERS.add(new ObjectTypeMapperImpl(Object.class, "Object")); for (ObjectTypeMapper handler : HANDLERS) { - final ObjectTypeMapper previous = - HANDLERS_BY_SERIAL_TYPE.put(handler.getHandledSerialType(), handler); + final ObjectTypeMapper previous = HANDLERS_BY_SERIAL_TYPE.put(handler.getHandledSerialType(), handler); if (previous != null) { throw new ConnectorException("More than one handler of the" + " same type: " + handler.getHandledSerialType()); @@ -63,8 +61,8 @@ public final class ObjectSerializerRegistry { /** * Mapping by class. Dynamically built since actual class may be a subclass. */ - private static final Map, ObjectTypeMapper> HANDLERS_BY_OBJECT_TYPE = Collections - .synchronizedMap(new WeakHashMap, ObjectTypeMapper>()); + private static final Map, ObjectTypeMapper> HANDLERS_BY_OBJECT_TYPE = + Collections.synchronizedMap(new WeakHashMap<>()); public static ObjectTypeMapper getMapperBySerialType(final String type) { return HANDLERS_BY_SERIAL_TYPE.get(type); diff --git a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/serializer/OperationMappings.java b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/serializer/OperationMappings.java index 1606c01e..9896f9e5 100644 --- a/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/serializer/OperationMappings.java +++ b/java/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/serializer/OperationMappings.java @@ -20,50 +20,49 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2017-2022 Evolveum + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.impl.serializer; import java.util.ArrayList; import java.util.List; - -import org.identityconnectors.framework.api.operations.*; - +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.DiscoverConfigurationApiOp; +import org.identityconnectors.framework.api.operations.GetApiOp; +import org.identityconnectors.framework.api.operations.LiveSyncApiOp; +import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp; +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.UpdateDeltaApiOp; +import org.identityconnectors.framework.api.operations.ValidateApiOp; class OperationMappings { - public static final List MAPPINGS = - new ArrayList(); + public static final List MAPPINGS = new ArrayList<>(); static { - MAPPINGS.add(new ObjectTypeMapperImpl(AuthenticationApiOp.class, - "AuthenticationApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(ResolveUsernameApiOp.class, - "ResolveUsernameApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(SearchApiOp.class, - "SearchApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(ValidateApiOp.class, - "ValidateApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(CreateApiOp.class, - "CreateApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(SchemaApiOp.class, - "SchemaApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(UpdateApiOp.class, - "UpdateApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(UpdateDeltaApiOp.class, - "UpdateDeltaApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(DeleteApiOp.class, - "DeleteApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(GetApiOp.class, - "GetApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(TestApiOp.class, - "TestApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(ScriptOnResourceApiOp.class, - "ScriptOnResourceApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(ScriptOnConnectorApiOp.class, - "ScriptOnConnectorApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(SyncApiOp.class, - "SyncApiOp")); - MAPPINGS.add(new ObjectTypeMapperImpl(DiscoverConfigurationApiOp.class, - "DiscoverConfigurationApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(AuthenticationApiOp.class, "AuthenticationApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(ResolveUsernameApiOp.class, "ResolveUsernameApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(SearchApiOp.class, "SearchApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(ValidateApiOp.class, "ValidateApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(CreateApiOp.class, "CreateApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(SchemaApiOp.class, "SchemaApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(UpdateApiOp.class, "UpdateApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(UpdateDeltaApiOp.class, "UpdateDeltaApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(DeleteApiOp.class, "DeleteApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(GetApiOp.class, "GetApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(TestApiOp.class, "TestApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(ScriptOnResourceApiOp.class, "ScriptOnResourceApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(ScriptOnConnectorApiOp.class, "ScriptOnConnectorApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(SyncApiOp.class, "SyncApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(LiveSyncApiOp.class, "LiveSyncApiOp")); + MAPPINGS.add(new ObjectTypeMapperImpl(DiscoverConfigurationApiOp.class, "DiscoverConfigurationApiOp")); } } diff --git a/java/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/ConnectorInfoManagerTestBase.java b/java/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/ConnectorInfoManagerTestBase.java index 76340560..ba947345 100644 --- a/java/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/ConnectorInfoManagerTestBase.java +++ b/java/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/ConnectorInfoManagerTestBase.java @@ -53,6 +53,7 @@ import org.identityconnectors.framework.api.ConnectorKey; import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.api.operations.CreateApiOp; +import org.identityconnectors.framework.api.operations.LiveSyncApiOp; import org.identityconnectors.framework.api.operations.SearchApiOp; import org.identityconnectors.framework.api.operations.SyncApiOp; import org.identityconnectors.framework.common.FrameworkUtilTestHelpers; @@ -202,8 +203,9 @@ public void testAPIConfiguration() throws Exception { assertNotNull(property); Set> operations = property.getOperations(); - assertEquals(1, operations.size()); - assertEquals(SyncApiOp.class, operations.iterator().next()); + assertEquals(2, operations.size()); + assertTrue(operations.contains(SyncApiOp.class)); + assertTrue(operations.contains(LiveSyncApiOp.class)); CurrentLocale.clear(); assertEquals("Help for test field.", property.getHelpMessage(null)); diff --git a/java/connector-framework-internal/src/test/java/org/identityconnectors/mockconnector/MockAllOpsConnector.java b/java/connector-framework-internal/src/test/java/org/identityconnectors/mockconnector/MockAllOpsConnector.java index 8ab7e165..03acb481 100644 --- a/java/connector-framework-internal/src/test/java/org/identityconnectors/mockconnector/MockAllOpsConnector.java +++ b/java/connector-framework-internal/src/test/java/org/identityconnectors/mockconnector/MockAllOpsConnector.java @@ -20,13 +20,14 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2014 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.mockconnector; import java.util.Set; - import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.ResultsHandler; @@ -44,6 +45,7 @@ import org.identityconnectors.framework.spi.operations.AuthenticateOp; import org.identityconnectors.framework.spi.operations.CreateOp; import org.identityconnectors.framework.spi.operations.DeleteOp; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; import org.identityconnectors.framework.spi.operations.ResolveUsernameOp; import org.identityconnectors.framework.spi.operations.ScriptOnConnectorOp; import org.identityconnectors.framework.spi.operations.ScriptOnResourceOp; @@ -55,7 +57,7 @@ public class MockAllOpsConnector extends MockConnector implements CreateOp, DeleteOp, UpdateOp, SearchOp, UpdateAttributeValuesOp, AuthenticateOp, ResolveUsernameOp, TestOp, - ScriptOnConnectorOp, ScriptOnResourceOp , SyncOp { + ScriptOnConnectorOp, ScriptOnResourceOp, SyncOp, LiveSyncOp { @Override public Object runScriptOnConnector(ScriptContext request, OperationOptions options) { @@ -74,8 +76,7 @@ public Object runScriptOnResource(ScriptContext request, OperationOptions option } @Override - public Uid create(final ObjectClass objectClass, final Set createAttributes, - OperationOptions options) { + public Uid create(final ObjectClass objectClass, final Set createAttributes, OperationOptions options) { assert createAttributes != null; addCall(createAttributes); return null; @@ -88,30 +89,31 @@ public void delete(final ObjectClass objectClass, final Uid uid, OperationOption } @Override - public Uid update(ObjectClass objectClass, Uid uid, Set attrs, - OperationOptions options) { + public Uid update(ObjectClass objectClass, Uid uid, Set attrs, OperationOptions options) { assert objectClass != null && attrs != null; addCall(objectClass, attrs); return null; } @Override - public Uid addAttributeValues(ObjectClass objclass, Uid uid, Set valuesToAdd, - OperationOptions options) { + public Uid addAttributeValues(ObjectClass objclass, Uid uid, Set valuesToAdd, OperationOptions options) { addCall(objclass, valuesToAdd); return null; } @Override - public Uid removeAttributeValues(ObjectClass objclass, Uid uid, Set valuesToRemove, + public Uid removeAttributeValues( + ObjectClass objclass, + Uid uid, + Set valuesToRemove, OperationOptions options) { + addCall(objclass, valuesToRemove); return null; } @Override - public FilterTranslator createFilterTranslator(ObjectClass objectClass, - OperationOptions options) { + public FilterTranslator createFilterTranslator(ObjectClass objectClass, OperationOptions options) { assert objectClass != null && options != null; addCall(objectClass, options); // no translation - ok since this is just for tests @@ -120,8 +122,7 @@ public FilterTranslator createFilterTranslator(ObjectClass objectClass, } @Override - public void executeQuery(ObjectClass objectClass, String query, ResultsHandler handler, - OperationOptions options) { + public void executeQuery(ObjectClass objectClass, String query, ResultsHandler handler, OperationOptions options) { assert objectClass != null && handler != null && options != null; addCall(objectClass, query, handler, options); if (null != options.getPageSize() && options.getPageSize() > 0) { @@ -131,8 +132,12 @@ public void executeQuery(ObjectClass objectClass, String query, ResultsHandler h } @Override - public Uid authenticate(ObjectClass objectClass, String username, GuardedString password, + public Uid authenticate( + ObjectClass objectClass, + String username, + GuardedString password, OperationOptions options) { + assert username != null && password != null; addCall(username, password); return null; @@ -171,4 +176,14 @@ public SyncToken getLatestSyncToken(ObjectClass objectClass) { addCall(objectClass); return new SyncToken(0); } + + @Override + public void livesync( + final ObjectClass objectClass, + final LiveSyncResultsHandler handler, + final OperationOptions options) { + + assert objectClass != null && handler != null && options != null; + addCall(objectClass, handler, options); + } } diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacade.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacade.java index 22c17c9a..969e865a 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacade.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacade.java @@ -21,17 +21,32 @@ * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. * Portions Copyrighted 2018-2022 Evolveum + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.api; import java.util.Set; - -import org.identityconnectors.framework.api.operations.*; +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.DiscoverConfigurationApiOp; +import org.identityconnectors.framework.api.operations.GetApiOp; +import org.identityconnectors.framework.api.operations.LiveSyncApiOp; +import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp; +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.UpdateDeltaApiOp; +import org.identityconnectors.framework.api.operations.ValidateApiOp; /** * Main interface through which an application invokes Connector operations. - * Represents at the API level a specific instance of a Connector that has been - * configured in a specific way. + * Represents at the API level a specific instance of a Connector that has been configured in a specific way. * * @see ConnectorFacadeFactory * @@ -40,7 +55,7 @@ */ public interface ConnectorFacade extends CreateApiOp, DeleteApiOp, SearchApiOp, UpdateApiOp, UpdateDeltaApiOp, SchemaApiOp, AuthenticationApiOp, ResolveUsernameApiOp, GetApiOp, ValidateApiOp, TestApiOp, - ScriptOnConnectorApiOp, ScriptOnResourceApiOp, SyncApiOp, DiscoverConfigurationApiOp { + ScriptOnConnectorApiOp, ScriptOnResourceApiOp, SyncApiOp, LiveSyncApiOp, DiscoverConfigurationApiOp { /** * Gets the unique generated identifier of this ConnectorFacade. @@ -63,7 +78,7 @@ public interface ConnectorFacade extends CreateApiOp, DeleteApiOp, SearchApiOp, * Get an instance of an operation that this facade supports. */ APIOperation getOperation(Class clazz); - + /** * Dispose of any resources associated with this facade (except for facade classes). * This will dispose of any connector instances in the connector pool. The purpose @@ -71,9 +86,8 @@ public interface ConnectorFacade extends CreateApiOp, DeleteApiOp, SearchApiOp, * But it can also be used to implement "logout and login" functionality for connectors, * e.g. in cases when server-side configuration has changed and the operator needs to * force closing and re-opening of all connector connections. - * + * * @since 1.5.0.0 */ void dispose(); - } diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacadeFactory.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacadeFactory.java index b618335c..70195358 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacadeFactory.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/api/ConnectorFacadeFactory.java @@ -43,6 +43,7 @@ public abstract class ConnectorFacadeFactory { "org.identityconnectors.framework.impl.api.ManagedConnectorFacadeFactoryImpl"; private static ConnectorFacadeFactory instance; + private static ConnectorFacadeFactory managedInstance; /** @@ -90,8 +91,7 @@ public static synchronized ConnectorFacadeFactory getManagedInstance() { * Get a new instance of {@link ConnectorFacade}. * * @param config - * all the configuration that the framework, connector, and - * pooling needs. + * all the configuration that the framework, connector, and pooling needs. * @return {@link ConnectorFacade} to call API operations against. */ public abstract ConnectorFacade newInstance(APIConfiguration config); @@ -99,12 +99,9 @@ public static synchronized ConnectorFacadeFactory getManagedInstance() { /** * Get a new instance of {@link ConnectorFacade}. * - * @param connectorInfo - * TODO Add JavaDoc later - * @param config - * all the configuration that the framework, connector, and - * pooling needs. It's a Base64 serialised APIConfiguration - * instance. + * @param connectorInfo TODO Add JavaDoc later + * @param config all the configuration that the framework, connector, and pooling needs. It's a Base64 serialised + * APIConfiguration instance. * @return {@link ConnectorFacade} to call API operations against. * @since 1.4 */ diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/api/operations/LiveSyncApiOp.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/api/operations/LiveSyncApiOp.java new file mode 100644 index 00000000..cab3b1af --- /dev/null +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/api/operations/LiveSyncApiOp.java @@ -0,0 +1,57 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 ConnId. 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.framework.api.operations; + +import org.identityconnectors.framework.common.objects.LiveSyncDelta; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; + +/** + * Poll for synchronization events--i.e., native changes to target objects. + *

+ * Connectors that implement {@linkplain LiveSyncOp the LiveSyncOp SPI} will support + * this. + * + * @see LiveSyncOp + */ +public interface LiveSyncApiOp extends APIOperation { + + /** + * Request synchronization events--i.e., native changes to target objects. + *

+ * This method will call the specified + * {@linkplain LiveSyncResultsHandler#handle handler} once to pass back each + * matching {@linkplain LiveSyncDelta synchronization event}. Once this method + * returns, this method will no longer invoke the specified handler. + * + * @param objectClass The class of object for which to return synchronization events. Must not be null. + * @param handler The result handler. Must not be null. + * @param options Options that affect the way this operation is run. May be null. + * @return The sync token or {@code null}. + * @throws IllegalArgumentException if {@code objectClass} or {@code handler} is null + * or if any argument is invalid. + */ + void livesync(ObjectClass objectClass, LiveSyncResultsHandler handler, OperationOptions options); +} diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/FrameworkUtil.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/FrameworkUtil.java index 0c9844d6..499af23c 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/FrameworkUtil.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/FrameworkUtil.java @@ -21,6 +21,7 @@ * ==================== * Portions Copyrighted 2010-2014 ForgeRock AS. * Portions Copyrighted 2017-2022 Evolveum + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.common; @@ -90,6 +91,7 @@ private FrameworkUtil() { SPI_TO_API.put(ScriptOnConnectorOp.class, ScriptOnConnectorApiOp.class); SPI_TO_API.put(ScriptOnResourceOp.class, ScriptOnResourceApiOp.class); SPI_TO_API.put(SyncOp.class, SyncApiOp.class); + SPI_TO_API.put(LiveSyncOp.class, LiveSyncApiOp.class); SPI_TO_API.put(DiscoverConfigurationOp.class, DiscoverConfigurationApiOp.class); } diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncDelta.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncDelta.java new file mode 100644 index 00000000..e84c0ab1 --- /dev/null +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncDelta.java @@ -0,0 +1,135 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 ConnId. 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.framework.common.objects; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.identityconnectors.common.Assertions; +import org.identityconnectors.framework.api.operations.LiveSyncApiOp; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; + +/** + * Represents a change to an object in a resource. + * + * @see LiveSyncApiOp + * @see LiveSyncOp + */ +public class LiveSyncDelta { + + private final ObjectClass objectClass; + + private final Uid uid; + + private final ConnectorObject object; + + LiveSyncDelta( + final ObjectClass objectClass, + final Uid uid, + final ConnectorObject object) { + + Assertions.nullCheck(uid, "uid"); + + // if object not null, make sure its Uid matches + if (object != null && !uid.attributeEquals(object.getUid())) { + throw new IllegalArgumentException("Uid does not match that of the object."); + } + if (object != null && !objectClass.equals(object.getObjectClass())) { + throw new IllegalArgumentException("ObjectClass does not match that of the object."); + } + + this.objectClass = objectClass; + this.uid = uid; + this.object = object; + } + + /** + * If the change described by this SyncDelta.DELETE and the + * deleted object value is null, this method returns the + * ObjectClass of the deleted object. If operation syncs + * {@link org.identityconnectors.framework.common.objects.ObjectClass#ALL} + * this must be set, otherwise this method can return null. + * + * @return the ObjectClass of the deleted object. + */ + public ObjectClass getObjectClass() { + return objectClass; + } + + /** + * Returns the Uid of the connector object that changed. + * + * @return The Uid. + */ + public Uid getUid() { + return uid; + } + + /** + * Returns the connector object that changed. This may be null in the case of delete. + * + * @return The object or possibly null if this represents a delete. + */ + public ConnectorObject getObject() { + return object; + } + + @Override + public String toString() { + Map values = new HashMap<>(); + values.put("ObjectClass", objectClass); + values.put("Uid", uid); + values.put("Object", object); + return values.toString(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + Objects.hashCode(this.objectClass); + hash = 67 * hash + Objects.hashCode(this.uid); + hash = 67 * hash + Objects.hashCode(this.object); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LiveSyncDelta other = (LiveSyncDelta) obj; + if (!Objects.equals(this.objectClass, other.objectClass)) { + return false; + } + if (!Objects.equals(this.uid, other.uid)) { + return false; + } + return Objects.equals(this.object, other.object); + } +} diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncDeltaBuilder.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncDeltaBuilder.java new file mode 100644 index 00000000..9d87cb0d --- /dev/null +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncDeltaBuilder.java @@ -0,0 +1,126 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 ConnId. 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.framework.common.objects; + +/** + * Builder for {@link LiveSyncDelta}. + */ +public final class LiveSyncDeltaBuilder { + + private ObjectClass objectClass; + + private Uid uid; + + private ConnectorObject connectorObject; + + /** + * Create a new LiveSyncDeltaBuilder + */ + public LiveSyncDeltaBuilder() { + } + + /** + * Creates a new LiveSyncDeltaBuilder whose values are initialized to those of the delta. + * + * @param delta the original delta. + */ + public LiveSyncDeltaBuilder(final LiveSyncDelta delta) { + connectorObject = delta.getObject(); + objectClass = delta.getObjectClass(); + uid = delta.getUid(); + } + + /** + * Gets the ObjectClass of the object that deleted. + * + * @return The ObjectClass of the object that deleted. + */ + public ObjectClass getObjectClass() { + return objectClass; + } + + /** + * Sets the ObjectClass of the object that deleted. Note that this is + * implicitly set when you call {@link #setObject(ConnectorObject)}. + * + * @param objectClass The ObjectClass of the object that changed. + */ + public LiveSyncDeltaBuilder setObjectClass(final ObjectClass objectClass) { + this.objectClass = objectClass; + return this; + } + + /** + * Gets the Uid of the object that changed. + * + * @return The Uid of the object that changed. + */ + public Uid getUid() { + return uid; + } + + /** + * Sets the Uid of the object that changed. Note that this is implicitly set + * when you call {@link #setObject(ConnectorObject)}. + * + * @param uid The Uid of the object that changed. + */ + public LiveSyncDeltaBuilder setUid(final Uid uid) { + this.uid = uid; + return this; + } + + /** + * Returns the object that changed. + * + * @return The object that changed. May be null for deletes. + */ + public ConnectorObject getObject() { + return connectorObject; + } + + /** + * Sets the object that changed and implicitly sets Uid if object is not null. + * + * @param object The object that changed. May be null for deletes. + */ + public LiveSyncDeltaBuilder setObject(final ConnectorObject object) { + connectorObject = object; + if (object != null) { + uid = object.getUid(); + objectClass = object.getObjectClass(); + } + return this; + } + + /** + * Creates a LiveSyncDelta. Prior to calling the following must be specified: + *

    + *
  1. {@link #setObject(ConnectorObject) Object}
  2. + *
  3. {@link #setUid(Uid) Uid} (this is implictly set when calling {@link #setObject(ConnectorObject)})
  4. + *
+ */ + public LiveSyncDelta build() { + return new LiveSyncDelta(objectClass, uid, connectorObject); + } +} diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncResultsHandler.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncResultsHandler.java new file mode 100644 index 00000000..c8a8a89a --- /dev/null +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/LiveSyncResultsHandler.java @@ -0,0 +1,45 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 ConnId. 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.framework.common.objects; + +import org.identityconnectors.framework.api.operations.LiveSyncApiOp; + +/** + * Callback interface that an application implements in order to handle results + * from {@link LiveSyncApiOp} in a stream-processing fashion. + */ +public interface LiveSyncResultsHandler { + + /** + * Called to handle a delta in the stream. The Connector framework will call this method multiple times, once for + * each result. Although this method is callback, the framework will invoke it synchronously. Thus, the framework + * guarantees that once an application's call to {@link LiveSyncApiOp#sync LiveSyncApiOp#sync()} returns, the + * framework will no longer call this method to handle results from that {@code sync} operation. + * + * @param delta The change + * @return True if the application wants to continue processing more results. + * @throws RuntimeException If the application encounters an exception. This will stop iteration and the exception + * will propagate to the application. + */ + boolean handle(LiveSyncDelta delta); +} diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/ObjectClass.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/ObjectClass.java index a31192f9..65c37f9d 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/ObjectClass.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/ObjectClass.java @@ -20,6 +20,7 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2014 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.common.objects; @@ -28,6 +29,8 @@ import static org.identityconnectors.framework.common.objects.ObjectClassUtil.createSpecialName; import java.util.Locale; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; +import org.identityconnectors.framework.spi.operations.SyncOp; /** * An instance of ObjectClass specifies a category or type @@ -42,7 +45,6 @@ public final class ObjectClass { // ======================================================================= // Basic Types--i.e., common values of the ObjectClass attribute. // ======================================================================= - /** * This constant defines a specific {@linkplain #getObjectClassValue value * of ObjectClass} that is reserved for {@link ObjectClass#ACCOUNT}. @@ -64,7 +66,6 @@ public final class ObjectClass { // ======================================================================= // Create only after all other static initializers // ======================================================================= - /** * Represents a human being in the context of a specific system or * application. @@ -93,9 +94,11 @@ public final class ObjectClass { * Represents all collections that contains any object. *

* This constant allowed to use in operation - * {@link org.identityconnectors.framework.spi.operations.SyncOp#getLatestSyncToken(ObjectClass)} + * {@link SyncOp#getLatestSyncToken(ObjectClass)} + * and + * {@link SyncOp#sync(ObjectClass, SyncToken, SyncResultsHandler, OperationOptions)} * and - * {@link org.identityconnectors.framework.spi.operations.SyncOp#sync(ObjectClass, SyncToken, SyncResultsHandler, OperationOptions)} + * {@link LiveSyncOp#sync(ObjectClass, LiveSyncResultsHandler, OperationOptions)} * any other operation throws {@link UnsupportedOperationException} */ public static final ObjectClass ALL = new ObjectClass(ALL_NAME); @@ -105,8 +108,7 @@ public final class ObjectClass { /** * Create a custom object class. * - * @param type - * string representation for the name of the object class. + * @param type string representation for the name of the object class. */ public ObjectClass(String type) { if (type == null) { @@ -136,11 +138,9 @@ public String getDisplayNameKey() { /** * Determines if the 'name' matches this {@link ObjectClass}. * - * @param name - * case-insensitive string representation of the ObjectClass's - * type. + * @param name case-insensitive string representation of the ObjectClass's type. * @return true if the case-insensitive name is equal to that - * of the one in this {@link ObjectClass}. + * of the one in this {@link ObjectClass}. */ public boolean is(String name) { return namesEqual(type, name); @@ -168,15 +168,11 @@ public boolean equals(Object obj) { ObjectClass other = (ObjectClass) obj; - if (!is(other.getObjectClassValue())) { - return false; - } - return true; + return is(other.getObjectClassValue()); } @Override public String toString() { return "ObjectClass: " + type; } - } diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDelta.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDelta.java index 0180ce0f..2631d0b4 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDelta.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDelta.java @@ -20,11 +20,13 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2014 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.common.objects; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.identityconnectors.common.Assertions; import org.identityconnectors.framework.api.operations.SyncApiOp; @@ -36,31 +38,34 @@ * @see SyncApiOp * @see SyncOp */ -public final class SyncDelta { +public final class SyncDelta extends LiveSyncDelta { + private final SyncToken token; + private final SyncDeltaType deltaType; + private final Uid previousUid; - private final ObjectClass objectClass; - private final Uid uid; - private final ConnectorObject connectorObject; /** * Creates a SyncDelta. * - * @param token - * The token. Must not be null. - * @param deltaType - * The delta. Must not be null. - * @param uid - * The uid. Must not be null. - * @param object - * The object that has changed. May be null for delete. + * @param token The token. Must not be null. + * @param deltaType The delta. Must not be null. + * @param uid The uid. Must not be null. + * @param object The object that has changed. May be null for delete. */ - SyncDelta(SyncToken token, SyncDeltaType deltaType, Uid previousUid, ObjectClass objectClass, - Uid uid, ConnectorObject object) { + SyncDelta( + final SyncToken token, + final SyncDeltaType deltaType, + final Uid previousUid, + final ObjectClass objectClass, + final Uid uid, + final ConnectorObject object) { + + super(objectClass, uid, object); + Assertions.nullCheck(token, "token"); Assertions.nullCheck(deltaType, "deltaType"); - Assertions.nullCheck(uid, "uid"); // do not allow previous Uid for anything else than create or update if (previousUid != null && (deltaType == SyncDeltaType.DELETE || deltaType == SyncDeltaType.CREATE)) { @@ -74,67 +79,21 @@ public final class SyncDelta { "ConnectorObject must be specified for anything other than delete."); } - // if object not null, make sure its Uid matches - if (object != null && !uid.attributeEquals(object.getUid())) { - throw new IllegalArgumentException("Uid does not match that of the object."); - } - if (object != null && !objectClass.equals(object.getObjectClass())) { - throw new IllegalArgumentException("ObjectClass does not match that of the object."); - } - this.token = token; this.deltaType = deltaType; this.previousUid = previousUid; - this.objectClass = objectClass; - this.uid = uid; - connectorObject = object; } /** - * If the change described by this SyncDelta modified the - * object's Uid, this method returns the Uid before the change. Not all - * resources can determine the previous Uid, so this method can return - * null. + * If the change described by this SyncDelta modified the object's Uid, this method returns the Uid + * before the change. Not all resources can determine the previous Uid, so this method can return {@code null}. * - * @return the previous Uid or null if it could not be determined or the - * change did not modify the Uid. + * @return the previous Uid or null if it could not be determined or the change did not modify the Uid. */ public Uid getPreviousUid() { return previousUid; } - /** - * If the change described by this SyncDelta.DELETE and the - * deleted object value is null, this method returns the - * ObjectClass of the deleted object. If operation syncs - * {@link org.identityconnectors.framework.common.objects.ObjectClass#ALL} - * this must be set, otherwise this method can return null. - * - * @return the ObjectClass of the deleted object. - */ - public ObjectClass getObjectClass() { - return objectClass; - } - - /** - * Returns the Uid of the connector object that changed. - * - * @return The Uid. - */ - public Uid getUid() { - return uid; - } - - /** - * Returns the connector object that changed. This may be null in the case - * of delete. - * - * @return The object or possibly null if this represents a delete. - */ - public ConnectorObject getObject() { - return connectorObject; - } - /** * Returns the SyncToken of the object that changed. * @@ -155,63 +114,46 @@ public SyncDeltaType getDeltaType() { @Override public String toString() { - Map values = new HashMap(); + Map values = new HashMap<>(); values.put("Token", token); values.put("DeltaType", deltaType); values.put("PreviousUid", previousUid); - values.put("ObjectClass", objectClass); - values.put("Uid", uid); - values.put("Object", connectorObject); + values.put("ObjectClass", getObjectClass()); + values.put("Uid", getUid()); + values.put("Object", getObject()); return values.toString(); } @Override public int hashCode() { - int result = token.hashCode(); - result = 31 * result + deltaType.hashCode(); - result = 31 * result + (previousUid != null ? previousUid.hashCode() : 0); - result = 31 * result + (objectClass != null ? objectClass.hashCode() : 0); - result = 31 * result + uid.hashCode(); - result = 31 * result + (connectorObject != null ? connectorObject.hashCode() : 0); - return result; + int hash = super.hashCode(); + hash = 89 * hash + Objects.hashCode(this.token); + hash = 89 * hash + Objects.hashCode(this.deltaType); + hash = 89 * hash + Objects.hashCode(this.previousUid); + return hash; } @Override - public boolean equals(Object o) { - if (o instanceof SyncDelta) { - SyncDelta other = (SyncDelta) o; - if (!token.equals(other.token)) { - return false; - } - if (!deltaType.equals(other.deltaType)) { - return false; - } - if (previousUid == null) { - if (other.previousUid != null) { - return false; - } - } else if (!previousUid.equals(other.previousUid)) { - return false; - } - if (objectClass == null) { - if (other.objectClass != null) { - return false; - } - } else if (!objectClass.equals(other.objectClass)) { - return false; - } - if (!uid.equals(other.uid)) { - return false; - } - if (connectorObject == null) { - if (other.connectorObject != null) { - return false; - } - } else if (!connectorObject.equals(other.connectorObject)) { - return false; - } + public boolean equals(Object obj) { + if (this == obj) { return true; } - return false; + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final SyncDelta other = (SyncDelta) obj; + if (!super.equals(obj)) { + return false; + } + if (!Objects.equals(this.token, other.token)) { + return false; + } + if (this.deltaType != other.deltaType) { + return false; + } + return Objects.equals(this.previousUid, other.previousUid); } } diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDeltaBuilder.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDeltaBuilder.java index 7b7dc828..8b8faf2d 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDeltaBuilder.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/SyncDeltaBuilder.java @@ -20,6 +20,7 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2014 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.common.objects; @@ -27,26 +28,29 @@ * Builder for {@link SyncDelta}. */ public final class SyncDeltaBuilder { + private SyncToken syncToken; + private SyncDeltaType deltaType; + private Uid previousUid; + private ObjectClass objectClass; + private Uid uid; + private ConnectorObject connectorObject; /** * Create a new SyncDeltaBuilder */ public SyncDeltaBuilder() { - } /** - * Creates a new SyncDeltaBuilder whose values are initialized - * to those of the delta. + * Creates a new SyncDeltaBuilder whose values are initialized to those of the delta. * - * @param delta - * The original delta. + * @param delta the original delta. */ public SyncDeltaBuilder(SyncDelta delta) { syncToken = delta.getToken(); @@ -69,8 +73,7 @@ public SyncToken getToken() { /** * Sets the SyncToken of the object that changed. * - * @param token - * the SyncToken of the object that changed. + * @param token the SyncToken of the object that changed. */ public SyncDeltaBuilder setToken(SyncToken token) { syncToken = token; @@ -89,8 +92,7 @@ public SyncDeltaType getDeltaType() { /** * Sets the type of the change that occurred. * - * @param type - * The type of change that occurred. + * @param type The type of change that occurred. */ public SyncDeltaBuilder setDeltaType(SyncDeltaType type) { deltaType = type; @@ -109,8 +111,7 @@ public Uid getPreviousUid() { /** * Sets the Uid of the object before the change. * - * @param previousUid - * The Uid of the object before the change. + * @param previousUid The Uid of the object before the change. */ public SyncDeltaBuilder setPreviousUid(Uid previousUid) { this.previousUid = previousUid; @@ -130,8 +131,7 @@ public ObjectClass getObjectClass() { * Sets the ObjectClass of the object that deleted. Note that this is * implicitly set when you call {@link #setObject(ConnectorObject)}. * - * @param objectClass - * The ObjectClass of the object that changed. + * @param objectClass The ObjectClass of the object that changed. */ public SyncDeltaBuilder setObjectClass(ObjectClass objectClass) { this.objectClass = objectClass; @@ -151,8 +151,7 @@ public Uid getUid() { * Sets the Uid of the object that changed. Note that this is implicitly set * when you call {@link #setObject(ConnectorObject)}. * - * @param uid - * The Uid of the object that changed. + * @param uid The Uid of the object that changed. */ public SyncDeltaBuilder setUid(Uid uid) { this.uid = uid; @@ -169,11 +168,9 @@ public ConnectorObject getObject() { } /** - * Sets the object that changed and implicitly sets Uid if object is not - * null. + * Sets the object that changed and implicitly sets Uid if object is not null. * - * @param object - * The object that changed. May be null for deletes. + * @param object The object that changed. May be null for deletes. */ public SyncDeltaBuilder setObject(ConnectorObject object) { connectorObject = object; diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/AttributeNormalizer.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/AttributeNormalizer.java index 28e1235b..8a715e48 100644 --- a/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/AttributeNormalizer.java +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/AttributeNormalizer.java @@ -19,12 +19,14 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.framework.spi; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.spi.operations.CreateOp; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; import org.identityconnectors.framework.spi.operations.SearchOp; import org.identityconnectors.framework.spi.operations.SyncOp; import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp; @@ -41,6 +43,7 @@ *

  • The filter passed to {@link SearchOp}.
  • *
  • The results returned from {@link SearchOp}.
  • *
  • The results returned from {@link SyncOp}.
  • + *
  • The results returned from {@link LiveSyncOp}.
  • *
  • The attributes passed to {@link UpdateAttributeValuesOp}.
  • *
  • The Uid returned from {@link UpdateAttributeValuesOp}.
  • *
  • The attributes passed to {@link UpdateOp}.
  • diff --git a/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/operations/LiveSyncOp.java b/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/operations/LiveSyncOp.java new file mode 100644 index 00000000..cc24fb68 --- /dev/null +++ b/java/connector-framework/src/main/java/org/identityconnectors/framework/spi/operations/LiveSyncOp.java @@ -0,0 +1,54 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 ConnId. 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.framework.spi.operations; + +import org.identityconnectors.framework.api.operations.LiveSyncApiOp; +import org.identityconnectors.framework.common.objects.LiveSyncDelta; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; + +/** + * Poll for synchronization events--i.e., native changes to target objects. + * + * @see LiveSyncApiOp + */ +public interface LiveSyncOp extends SPIOperation { + + /** + * Request synchronization events--i.e., native changes to target objects. + *

    + * This method will call the specified {@linkplain LiveSyncResultsHandler#handle handler} once to pass back each + * matching {@linkplain LiveSyncDelta synchronization event}. Once this method returns, this method will no longer + * invoke the specified handler. + * + * @param objectClass The class of object for which to return synchronization events. Must not be null. + * @param handler The result handler. Must not be null. + * @param options Options that affect the way this operation is run. If the caller passes {@code null}, the + * framework will convert this into an empty set of options, so an implementation need not guard against this being + * null. + * @throws IllegalArgumentException if {@code objectClass} or {@code handler} is null or if any argument is + * invalid. + */ + void livesync(ObjectClass objectClass, LiveSyncResultsHandler handler, OperationOptions options); +} diff --git a/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java b/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java index 5f66f77e..71ba057e 100644 --- a/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java +++ b/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java @@ -19,8 +19,8 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== + * Portions Copyrighted 2024 ConnId */ - package org.identityconnectors.testconnector; import java.util.ArrayList; @@ -34,7 +34,6 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; - import org.identityconnectors.framework.common.exceptions.AlreadyExistsException; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException; @@ -46,6 +45,7 @@ import org.identityconnectors.framework.common.objects.AttributesAccessor; import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationOptions; @@ -62,12 +62,14 @@ import org.identityconnectors.framework.spi.SyncTokenResultsHandler; import org.identityconnectors.framework.spi.operations.CreateOp; import org.identityconnectors.framework.spi.operations.DeleteOp; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; import org.identityconnectors.framework.spi.operations.SearchOp; import org.identityconnectors.framework.spi.operations.SyncOp; -public abstract class TstAbstractConnector implements CreateOp, SearchOp, SyncOp, DeleteOp { +public abstract class TstAbstractConnector implements CreateOp, SearchOp, SyncOp, LiveSyncOp, DeleteOp { private static final class ResourceComparator implements Comparator { + private final List sortKeys; private ResourceComparator(final SortKey... sortKeys) { @@ -119,6 +121,7 @@ private List getValuesSorted(final ConnectorObject resource, final Strin } private static final Comparator VALUE_COMPARATOR = new Comparator() { + @Override public int compare(final Object o1, final Object o2) { return compareValues(o1, o2); @@ -182,9 +185,9 @@ public void delete(ObjectClass objectClass, Uid uid, OperationOptions options) { } @Override - public FilterTranslator createFilterTranslator(ObjectClass objectClass, - OperationOptions options) { + public FilterTranslator createFilterTranslator(ObjectClass objectClass, OperationOptions options) { return new FilterTranslator() { + @Override public List translate(Filter filter) { return Collections.singletonList(filter); @@ -193,17 +196,14 @@ public List translate(Filter filter) { } @Override - public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler handler, - OperationOptions options) { - + public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler handler, OperationOptions options) { SortKey[] sortKeys = options.getSortKeys(); if (null == sortKeys) { sortKeys = new SortKey[] { new SortKey(Name.NAME, true) }; } // Rebuild the full result set. - TreeSet resultSet = - new TreeSet(new ResourceComparator(sortKeys)); + TreeSet resultSet = new TreeSet<>(new ResourceComparator(sortKeys)); if (null != query) { for (ConnectorObject co : collection.values()) { @@ -220,9 +220,8 @@ public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler h // Paged Search final String pagedResultsCookie = options.getPagedResultsCookie(); String currentPagedResultsCookie = options.getPagedResultsCookie(); - final Integer pagedResultsOffset = - null != options.getPagedResultsOffset() ? Math.max(0, options - .getPagedResultsOffset()) : 0; + final Integer pagedResultsOffset = null != options.getPagedResultsOffset() + ? Math.max(0, options.getPagedResultsOffset()) : 0; final Integer pageSize = options.getPageSize(); int index = 0; @@ -273,12 +272,10 @@ public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler h ((SearchResultsHandler) handler).handleResult(new SearchResult()); } } - } @Override - public void sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, - OperationOptions options) { + public void sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, OperationOptions options) { if (handler instanceof SyncTokenResultsHandler) { ((SyncTokenResultsHandler) handler).handleResult(getLatestSyncToken(objectClass)); } @@ -289,8 +286,13 @@ public SyncToken getLatestSyncToken(ObjectClass objectClass) { return new SyncToken(config.getGuid().toString()); } - private final static SortedMap collection = - new TreeMap(String.CASE_INSENSITIVE_ORDER); + @Override + public void livesync(ObjectClass objectClass, LiveSyncResultsHandler handler, OperationOptions options) { + // nothing to do + } + + private final static SortedMap collection = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + static { boolean enabled = true; for (int i = 0; i < 100; i++) { @@ -298,7 +300,7 @@ public SyncToken getLatestSyncToken(ObjectClass objectClass) { builder.setUid(String.valueOf(i)); builder.setName(String.format("user%03d", i)); builder.addAttribute(AttributeBuilder.buildEnabled(enabled)); - Map mapAttribute = new HashMap(); + Map mapAttribute = new HashMap<>(); mapAttribute.put("email", "foo@example.com"); mapAttribute.put("primary", true); mapAttribute.put("usage", Arrays.asList("home", "work")); diff --git a/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnector.java b/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnector.java index 4c5bfdc1..ff48b26b 100644 --- a/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnector.java +++ b/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnector.java @@ -20,12 +20,13 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.testconnector; import java.util.List; import java.util.Set; - +import java.util.UUID; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeInfoBuilder; import org.identityconnectors.framework.common.objects.ConnectorObject; @@ -46,6 +47,8 @@ import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.AttributeInfo.RoleInReference; +import org.identityconnectors.framework.common.objects.LiveSyncDeltaBuilder; +import org.identityconnectors.framework.common.objects.LiveSyncResultsHandler; import org.identityconnectors.framework.common.objects.filter.AbstractFilterTranslator; import org.identityconnectors.framework.common.objects.filter.FilterTranslator; import org.identityconnectors.framework.spi.Configuration; @@ -54,6 +57,7 @@ import org.identityconnectors.framework.spi.SearchResultsHandler; import org.identityconnectors.framework.spi.SyncTokenResultsHandler; import org.identityconnectors.framework.spi.operations.CreateOp; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; import org.identityconnectors.framework.spi.operations.SchemaOp; import org.identityconnectors.framework.spi.operations.SearchOp; import org.identityconnectors.framework.spi.operations.SyncOp; @@ -63,7 +67,7 @@ displayNameKey = "TestConnector", categoryKey = "TestConnector.category", configurationClass = TstConnectorConfig.class) -public class TstConnector implements CreateOp, PoolableConnector, SchemaOp, SearchOp, SyncOp { +public class TstConnector implements CreateOp, PoolableConnector, SchemaOp, SearchOp, SyncOp, LiveSyncOp { public static final String USER_CLASS_NAME = "user"; @@ -273,14 +277,12 @@ public void sync( checkClassLoader(); int remaining = _config.getNumResults(); for (int i = 0; i < _config.getNumResults(); i++) { - ConnectorObjectBuilder obuilder = - new ConnectorObjectBuilder(); + ConnectorObjectBuilder obuilder = new ConnectorObjectBuilder(); obuilder.setUid(Integer.toString(i)); obuilder.setName(Integer.toString(i)); obuilder.setObjectClass(objectClass); - SyncDeltaBuilder builder = - new SyncDeltaBuilder(); + SyncDeltaBuilder builder = new SyncDeltaBuilder(); builder.setObject(obuilder.build()); builder.setDeltaType(SyncDeltaType.CREATE_OR_UPDATE); builder.setToken(new SyncToken("mytoken")); @@ -302,6 +304,23 @@ public SyncToken getLatestSyncToken(ObjectClass objectClass) { return new SyncToken("mylatest"); } + @Override + public void livesync( + ObjectClass objectClass, + LiveSyncResultsHandler handler, + OperationOptions options) { + + checkClassLoader(); + + handler.handle(new LiveSyncDeltaBuilder(). + setObject(new ConnectorObjectBuilder(). + setUid(UUID.randomUUID().toString()). + setName(UUID.randomUUID().toString()). + setObjectClass(objectClass). + build()). + build()); + } + @Override public Schema schema() { checkClassLoader(); diff --git a/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnectorConfig.java b/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnectorConfig.java index 4c1bda8e..4d7568ee 100644 --- a/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnectorConfig.java +++ b/java/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstConnectorConfig.java @@ -20,6 +20,7 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. + * Portions Copyrighted 2024 ConnId */ package org.identityconnectors.testconnector; @@ -27,10 +28,13 @@ import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.spi.AbstractConfiguration; import org.identityconnectors.framework.spi.ConfigurationProperty; +import org.identityconnectors.framework.spi.operations.LiveSyncOp; import org.identityconnectors.framework.spi.operations.SyncOp; public class TstConnectorConfig extends AbstractConfiguration { + private String tstField; + private String tst1Field; private int numResults; @@ -43,18 +47,17 @@ public TstConnectorConfig() { TstConnector.checkClassLoader(); } - public boolean getResetConnectionCount() { TstConnector.checkClassLoader(); return resetConnectionCount; } - public void setResetConnectionCount( boolean count ) { + public void setResetConnectionCount(boolean count) { TstConnector.checkClassLoader(); resetConnectionCount = count; } - @ConfigurationProperty(operations={SyncOp.class}) + @ConfigurationProperty(operations = { SyncOp.class, LiveSyncOp.class }) public String getTstField() { TstConnector.checkClassLoader(); return tstField; @@ -99,8 +102,7 @@ public void setFailValidation(boolean fail) { public void validate() { TstConnector.checkClassLoader(); if (failValidation) { - throw new ConnectorException("validation failed "+CurrentLocale.get().getLanguage()); + throw new ConnectorException("validation failed " + CurrentLocale.get().getLanguage()); } } - }