diff --git a/OpenICF-csvfile-connector/src/main/java/org/forgerock/openicf/csvfile/CSVFileConnector.java b/OpenICF-csvfile-connector/src/main/java/org/forgerock/openicf/csvfile/CSVFileConnector.java index f0a2b0a29..8a87e7c4b 100644 --- a/OpenICF-csvfile-connector/src/main/java/org/forgerock/openicf/csvfile/CSVFileConnector.java +++ b/OpenICF-csvfile-connector/src/main/java/org/forgerock/openicf/csvfile/CSVFileConnector.java @@ -14,6 +14,7 @@ * Copyright 2015-2016 ForgeRock AS * Portions Copyright 2011 Viliam Repan * Portions Copyright 2011 Radovan Semancik + * Portions Copyright 2026 3A Systems, LLC. */ package org.forgerock.openicf.csvfile; @@ -881,6 +882,9 @@ private ConnectorObject findObjectInFile(File file, Uid uid) { } private SyncDelta generateSyncDelta(ConnectorObject origin, ConnectorObject current, SyncToken token) { + if (origin == null && current == null) { + throw new IllegalArgumentException("Either the original or the current object is required"); + } SyncDeltaBuilder builder = new SyncDeltaBuilder(); builder.setUid(origin == null ? current.getUid() : origin.getUid()); builder.setToken(token); diff --git a/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableConnector.java b/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableConnector.java index ed3722c3b..f38272f4b 100644 --- a/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableConnector.java +++ b/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableConnector.java @@ -459,7 +459,7 @@ public Uid update(ObjectClass oclass, Uid uid, Set attrs, OperationOp log.info("Append empty attribute {0} for required columnName {1}", attributeName, columnName); value = DatabaseTableConstants.EMPTY_STR; } - final Integer sqlType = getColumnType(columnName); + final int sqlType = getColumnType(columnName); final SQLParam param = new SQLParam(quoteName(columnName), value, sqlType); updateSet.addBind(param); log.ok("Appended to update statement the attribute {0} for columnName {1} and sqlType {2}", attributeName, columnName, sqlType); @@ -609,7 +609,7 @@ public void sync(ObjectClass oclass, SyncToken token, SyncResultsHandler handler if(token != null && token.getValue() != null) { final Object tokenVal = token.getValue(); log.info("Sync token is {0}", tokenVal); - final Integer sqlType = getColumnType(config.getChangeLogColumn()); + final int sqlType = getColumnType(config.getChangeLogColumn()); where.addBind(new SQLParam(changeLogColumnName, tokenVal, sqlType),">" ); } final DatabaseQueryBuilder query = new DatabaseQueryBuilder(tblname, columnNames); @@ -1082,7 +1082,7 @@ private Set buildAttributeInfoSet(ResultSet rset) throws SQLExcep for (int i = 1; i <= count; i++) { final String name = meta.getColumnName(i); final AttributeInfoBuilder attrBld = new AttributeInfoBuilder(); - final Integer columnType = meta.getColumnType(i); + final int columnType = meta.getColumnType(i); log.ok("column name {0} has type {1}", name, columnType); columnSQLTypes.put(name, columnType); if (name.equalsIgnoreCase(config.getKeyColumn())) { diff --git a/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableFilterTranslator.java b/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableFilterTranslator.java index 8ca8ac64b..3feab988c 100644 --- a/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableFilterTranslator.java +++ b/OpenICF-databasetable-connector/src/main/java/org/identityconnectors/databasetable/DatabaseTableFilterTranslator.java @@ -57,7 +57,7 @@ public DatabaseTableFilterTranslator(DatabaseTableConnector connector, ObjectCla protected SQLParam getSQLParam(Attribute attribute, ObjectClass oclass, OperationOptions options) { final Object value = AttributeUtil.getSingleValue(attribute); final String columnName = connector.getColumnName(attribute.getName()); - final Integer columnType = connector.getColumnType(columnName); + final int columnType = connector.getColumnType(columnName); return new SQLParam(columnName, value,columnType); } diff --git a/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/PropertiesResolver.java b/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/PropertiesResolver.java index 78494c894..92b595276 100644 --- a/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/PropertiesResolver.java +++ b/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/PropertiesResolver.java @@ -130,7 +130,7 @@ private static String resolveValue(String value, Properties properties, int index = 0; int length = value.length(); StringBuffer result = new StringBuffer(); - while (index >= 0 && index < length) { + while (index < length) { int varStart = value.indexOf("${", index); if (varStart >= 0) { int varEnd = value.indexOf('}', varStart); diff --git a/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/SQLParam.java b/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/SQLParam.java index 9c4f98215..691d270b6 100644 --- a/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/SQLParam.java +++ b/OpenICF-dbcommon/src/main/java/org/identityconnectors/dbcommon/SQLParam.java @@ -19,9 +19,11 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.dbcommon; +import java.util.Objects; import java.sql.Types; /** @@ -109,8 +111,7 @@ public boolean equals(Object obj) { return false; } SQLParam other = (SQLParam) obj; - return (name == other.name || (name != null && name.equals(other.name))) - && (value == other.value || (value != null && value.equals(other.value))) + return Objects.equals(name, other.name) && Objects.equals(value, other.value) && sqlType == other.sqlType; } diff --git a/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnection.java b/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnection.java index 2c4e38cf1..e94e743ac 100644 --- a/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnection.java +++ b/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnection.java @@ -80,6 +80,7 @@ import org.forgerock.json.resource.CreateRequest; import org.forgerock.json.resource.DeleteRequest; import org.forgerock.json.resource.InternalServerErrorException; +import org.forgerock.json.resource.NotSupportedException; import org.forgerock.json.resource.PatchOperation; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.QueryRequest; @@ -492,6 +493,8 @@ private HttpUriRequest convert(Request request) throws ResourceException { rq = new HttpGet(builder.build()); break; } + default: + throw new NotSupportedException("Unsupported request type: " + request.getRequestType()); } } catch (URISyntaxException e) { throw new InternalServerErrorException(e); @@ -708,7 +711,7 @@ public HttpResponseResourceException(ResourceException wrapped) { } @Override - public ResourceException getCause() { + public synchronized ResourceException getCause() { return cause; } } diff --git a/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/scriptedcommon/ScriptedConfiguration.java b/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/scriptedcommon/ScriptedConfiguration.java index 8ea122ba8..ac39a5419 100644 --- a/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/scriptedcommon/ScriptedConfiguration.java +++ b/OpenICF-groovy-connector/src/main/java/org/forgerock/openicf/misc/scriptedcommon/ScriptedConfiguration.java @@ -782,24 +782,25 @@ protected Log getLogger(final Class clazz) { private GroovyScriptEngine groovyScriptEngine = null; - protected GroovyScriptEngine getGroovyScriptEngine() { + /** + * Synchronised for the whole initialisation, not double-checked: the + * customizer script runs against the half-initialised engine (it may call + * back into this configuration, which re-enters here), so other threads + * have to wait until it has run rather than see the engine early. + */ + protected synchronized GroovyScriptEngine getGroovyScriptEngine() { if (null == groovyScriptEngine) { - synchronized (this) { - if (null == groovyScriptEngine) { - - final CompilerConfiguration compilerConfiguration = - new CompilerConfiguration(config); - compilerConfiguration.addCompilationCustomizers(getImportCustomizer(null)); + final CompilerConfiguration compilerConfiguration = + new CompilerConfiguration(config); + compilerConfiguration.addCompilationCustomizers(getImportCustomizer(null)); - final GroovyClassLoader loader = - new GroovyClassLoader(getParentLoader(), compilerConfiguration, true); + final GroovyClassLoader loader = + new GroovyClassLoader(getParentLoader(), compilerConfiguration, true); - groovyScriptEngine = - new GroovyScriptEngine(getRoots(compilerConfiguration, loader), loader); + groovyScriptEngine = + new GroovyScriptEngine(getRoots(compilerConfiguration, loader), loader); - initializeCustomizer(); - } - } + initializeCustomizer(); } return groovyScriptEngine; } diff --git a/OpenICF-groovy-connector/src/test/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnectionTest.java b/OpenICF-groovy-connector/src/test/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnectionTest.java new file mode 100644 index 000000000..7c5c1e9f5 --- /dev/null +++ b/OpenICF-groovy-connector/src/test/java/org/forgerock/openicf/misc/crest/AbstractRemoteConnectionTest.java @@ -0,0 +1,98 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems, LLC. + */ +package org.forgerock.openicf.misc.crest; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.concurrent.Future; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; +import org.forgerock.json.JsonValue; +import org.forgerock.json.resource.NotSupportedException; +import org.forgerock.json.resource.QueryResourceHandler; +import org.forgerock.json.resource.QueryResponse; +import org.forgerock.json.resource.ReadRequest; +import org.forgerock.json.resource.RequestType; +import org.forgerock.json.resource.Requests; +import org.forgerock.json.resource.ResourceException; +import org.forgerock.json.resource.ResourcePath; +import org.forgerock.services.context.Context; +import org.forgerock.services.context.RootContext; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class AbstractRemoteConnectionTest { + + private static final class StubConnection extends AbstractRemoteConnection { + + StubConnection() { + super(ResourcePath.valueOf("api"), new HttpHost("localhost", 8080)); + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public Future execute(Context context, HttpUriRequest request, + HttpAsyncResponseConsumer responseConsumer, FutureCallback callback) { + throw new AssertionError("no HTTP request expected, got " + request); + } + + @Override + protected JsonValue parseJsonBody(HttpEntity entity, boolean allowEmpty) { + throw new AssertionError(); + } + + @Override + protected QueryResponse parseQueryResponse(HttpResponse response, + QueryResourceHandler handler) { + throw new AssertionError(); + } + } + + /** A read request that reports a type no {@code Connection} method produces. */ + private static ReadRequest apiTypedRequest() { + final ReadRequest real = Requests.newReadRequest("users/1"); + return (ReadRequest) Proxy.newProxyInstance(ReadRequest.class.getClassLoader(), + new Class[] { ReadRequest.class }, (proxy, method, args) -> { + if ("getRequestType".equals(method.getName())) { + return RequestType.API; + } + try { + return method.invoke(real, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + }); + } + + @Test + public void testUnsupportedRequestTypeFailsThePromise() throws InterruptedException { + try { + new StubConnection().readAsync(new RootContext(), apiTypedRequest()).getOrThrow(); + Assert.fail("NotSupportedException expected"); + } catch (ResourceException e) { + Assert.assertTrue(e instanceof NotSupportedException, String.valueOf(e)); + } + } +} diff --git a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/MultiOpTests.java b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/MultiOpTests.java index dabb83b72..19caf0bb7 100644 --- a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/MultiOpTests.java +++ b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/MultiOpTests.java @@ -21,6 +21,7 @@ * ==================== * * Portions Copyrighted 2012 ForgeRock AS + * Portions Copyrighted 2026 3A Systems, LLC * */ package org.identityconnectors.contract.test; @@ -32,6 +33,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashSet; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -102,7 +104,7 @@ protected void testRun(ObjectClass objectClass) { List> attrs = new ArrayList>(); // objects stored in connector resource before test - Map coBeforeTest = null; + Map coBeforeTest = Collections.emptyMap(); // sync variables SyncToken token = null; @@ -792,7 +794,7 @@ private ObjectClassInfo findOInfo(ObjectClass oclass) { */ private static boolean canLockOut() { // by default it's supposed that case insensitive search is disabled. - Boolean canLockout = true; + boolean canLockout = true; try { canLockout = !(Boolean) getDataProvider().getTestSuiteAttribute( SKIP + "." + LOCKOUT_PREFIX, diff --git a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/ObjectClassRunner.java b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/ObjectClassRunner.java index 2afec7a72..39c6f4e5a 100644 --- a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/ObjectClassRunner.java +++ b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/ObjectClassRunner.java @@ -227,6 +227,10 @@ public boolean isObjectClassSupported(ObjectClass objectClass) { oinfos = tmp; } } + if (oinfos == null) { + // no operation required: every object class of the schema qualifies + oinfos = getSchema().getObjectClassInfo(); + } // Find the objectclass in set of supported objectclasses (oinfos), // that is currently tested. If it is present set the indicator _ocSupported accordingly. diff --git a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SearchApiOpTests.java b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SearchApiOpTests.java index 55f7cfe26..d14cb26af 100644 --- a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SearchApiOpTests.java +++ b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SearchApiOpTests.java @@ -21,6 +21,7 @@ * ==================== * * Portions Copyrighted 2012 ForgeRock AS + * Portions Copyrighted 2026 3A Systems, LLC * */ package org.identityconnectors.contract.test; @@ -416,7 +417,7 @@ static String changeCase(String str_uid) { */ protected static boolean canSearchCaseInsensitive() { // by default it's supposed that case insensitive search is disabled. - Boolean canSearchCIns = true; + boolean canSearchCIns = true; try { canSearchCIns = !(Boolean) getDataProvider().getTestSuiteAttribute( DISABLE + "." + CASE_INSENSITIVE_PREFIX, diff --git a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SyncApiOpTests.java b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SyncApiOpTests.java index bcc2098d5..822fc7a90 100644 --- a/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SyncApiOpTests.java +++ b/OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/SyncApiOpTests.java @@ -21,6 +21,7 @@ * ==================== * * Portions Copyrighted 2012 ForgeRock AS + * Portions Copyrighted 2026 3A Systems, LLC * */ package org.identityconnectors.contract.test; @@ -314,7 +315,7 @@ public String getTestName() { */ protected static boolean canSyncAfterOp(Class operation) { // by default it's supposed that sync works for all change types - Boolean canSync = true; + boolean canSync = true; try { if (operation.equals(CreateApiOp.class)) { canSync = !(Boolean) getDataProvider().getTestSuiteAttribute( diff --git a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/ObjectPool.java b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/ObjectPool.java index 3fa45f994..882bc928b 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/ObjectPool.java +++ b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/ObjectPool.java @@ -247,15 +247,13 @@ public ObjectPoolEntry borrowObject() { try { handler.testObject(rv.getPooledObject()); } catch (Exception e) { - if (null != rv) { - dispose(rv); - // if it's a new object, break out of the loop - // immediately - if (rv.isNew()) { - throw ConnectorException.wrap(e); - } - rv = null; + dispose(rv); + // if it's a new object, break out of the loop + // immediately + if (rv.isNew()) { + throw ConnectorException.wrap(e); } + rv = null; } } while (null == rv); rv.setActive(true); diff --git a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/OperationalContext.java b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/OperationalContext.java index 24cef5ed6..37b0c4daa 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/OperationalContext.java +++ b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/OperationalContext.java @@ -85,14 +85,16 @@ public Configuration getConfiguration() { if (null == configuration) { synchronized (this) { if (null == configuration) { - this.configuration = + // fully set up before it is published to other threads + final Configuration bean = JavaClassProperties.createBean(apiConfiguration .getConfigurationProperties(), connectorInfo .getConnectorConfigurationClass()); if (null != apiConfiguration.getChangeListener() - && configuration instanceof AbstractConfiguration) { - ((AbstractConfiguration) configuration).addChangeCallback(this); + && bean instanceof AbstractConfiguration) { + ((AbstractConfiguration) bean).addChangeCallback(this); } + this.configuration = bean; } } } diff --git a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java index e1f937b19..ad18e66f2 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java +++ b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/SyncImpl.java @@ -20,6 +20,7 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2015 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.framework.impl.api.local.operations; @@ -68,7 +69,7 @@ public SyncToken sync(final ObjectClass objectClass, final SyncToken token, final SyncResultsHandler handlerChain = handler; final AtomicReference result = new AtomicReference(null); - final Boolean doAll = ObjectClass.ALL.equals(objectClass); + final boolean doAll = ObjectClass.ALL.equals(objectClass); // SyncTokenResultsHandler handlerChain = ((SyncOp) getConnector()).sync(objectClass, token, new SyncTokenResultsHandler() { diff --git a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteWrappedException.java b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteWrappedException.java index 0f2dda6aa..2f9900c97 100755 --- a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteWrappedException.java +++ b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/remote/RemoteWrappedException.java @@ -162,7 +162,7 @@ public boolean is(Class expected) { */ @Override @SuppressWarnings("unchecked") - public RemoteWrappedException getCause() { + public synchronized RemoteWrappedException getCause() { Object o = exception.get(FIELD_CAUSE); if (o instanceof Map) { return new RemoteWrappedException((Map) o); diff --git a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/async/impl/BatchApiOpImpl.java b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/async/impl/BatchApiOpImpl.java index d3aeddfb0..17f255a5b 100644 --- a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/async/impl/BatchApiOpImpl.java +++ b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/async/impl/BatchApiOpImpl.java @@ -374,7 +374,8 @@ protected void handleOperationResponseMessages(WebSocketConnectionHolder sourceC private class CompletionListener extends Thread { private final AtomicBoolean running = new AtomicBoolean(false); - public void start() { + @Override + public synchronized void start() { // The token and "complete" responses are dispatched on pool threads and may // race here; only the first caller may actually start the thread. if (!running.compareAndSet(false, true)) { diff --git a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java index 3e26ee150..24db3f639 100644 --- a/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java +++ b/OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/client/ClientRemoteConnectorInfoManager.java @@ -537,7 +537,7 @@ private class ICFWebSocket extends SimpleWebSocket { protected final PromiseImpl connectPromise = PromiseImpl.create(); - protected final Queue listeners = + protected final Queue messageListeners = new ConcurrentLinkedQueue(); // Written on the handshake-processing pool thread, read by other message @@ -614,11 +614,11 @@ protected void tryClose() { } final boolean add(final OperationMessageListener listener) { - return listeners.add(listener); + return messageListeners.add(listener); } final boolean remove(final OperationMessageListener listener) { - return listeners.remove(listener); + return messageListeners.remove(listener); } public void onClose(DataFrame frame) { @@ -629,7 +629,7 @@ public void onClose(DataFrame frame) { + closing.getCode() + " - " + closing.getReason())); OperationMessageListener listener; try { - while ((listener = listeners.poll()) != null) { + while ((listener = messageListeners.poll()) != null) { listener.onClose(adapter, closing.getCode(), closing.getReason()); } } finally { @@ -641,35 +641,35 @@ public void onClose(DataFrame frame) { public void onConnect() { super.onConnect(); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onConnect(adapter); } } public void onMessage(byte[] data) { super.onMessage(data); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onMessage(adapter, data); } } public void onMessage(String text) { super.onMessage(text); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onMessage(adapter, text); } } public void onPing(DataFrame frame) { super.onPing(frame); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onPing(adapter, frame.getBytes()); } } public void onPong(DataFrame frame) { super.onPong(frame); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onPong(adapter, frame.getBytes()); } } diff --git a/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/common/IOUtil.java b/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/common/IOUtil.java index 9a9921718..c8bd861cc 100644 --- a/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/common/IOUtil.java +++ b/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/common/IOUtil.java @@ -41,6 +41,7 @@ import java.io.Writer; import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -293,14 +294,16 @@ public static byte[] getResourceAsBytes(final Class clazz, final String res) public static String getResourceAsString(final Class clazz, final String res, final Charset charset) { assert clazz != null && StringUtil.isNotBlank(res); + // checked before the stream opens: the reader would throw with the stream open + Assertions.nullCheck(charset, "charset"); String ret = null; final InputStream ins = getResourceAsStream(clazz, res); if (ins != null) { + final InputStreamReader rdr = new InputStreamReader(ins, charset); try { - final InputStreamReader rdr = new InputStreamReader(ins, charset); ret = readerToString(rdr); } finally { - quietClose(ins); + quietClose(rdr); } } return ret; @@ -713,11 +716,9 @@ public static byte[] readFileBytes(final File file) throws IOException { * if the file parameter is null. */ public static void writeFileUTF8(final File file, final String contents) throws IOException { - final Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF8); - try { + try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), + StandardCharsets.UTF_8)) { writer.write(contents); - } finally { - writer.close(); } } diff --git a/OpenICF-java-framework/connector-server-grizzly/src/main/java/org/forgerock/openicf/framework/server/grizzly/OpenICFWebSocketApplication.java b/OpenICF-java-framework/connector-server-grizzly/src/main/java/org/forgerock/openicf/framework/server/grizzly/OpenICFWebSocketApplication.java index e9aab3d07..98f41f9e3 100644 --- a/OpenICF-java-framework/connector-server-grizzly/src/main/java/org/forgerock/openicf/framework/server/grizzly/OpenICFWebSocketApplication.java +++ b/OpenICF-java-framework/connector-server-grizzly/src/main/java/org/forgerock/openicf/framework/server/grizzly/OpenICFWebSocketApplication.java @@ -167,7 +167,7 @@ protected void doClose() { public static class OpenICFWebSocket extends DefaultWebSocket { - protected final Queue listeners = + protected final Queue messageListeners = new ConcurrentLinkedQueue(); private final ConnectionPrincipal connectionPrincipal; @@ -246,11 +246,11 @@ public OpenICFWebSocket(final ProtocolHandler protocolHandler, } public final boolean add(final OperationMessageListener listener) { - return listeners.add(listener); + return messageListeners.add(listener); } public final boolean remove(final OperationMessageListener listener) { - return listeners.remove(listener); + return messageListeners.remove(listener); } public void onClose(DataFrame frame) { @@ -259,7 +259,7 @@ public void onClose(DataFrame frame) { OperationMessageListener listener; try { - while ((listener = listeners.poll()) != null) { + while ((listener = messageListeners.poll()) != null) { listener.onClose(adapter, closing.getCode(), closing.getReason()); } } finally { @@ -271,35 +271,35 @@ public void onClose(DataFrame frame) { public void onConnect() { super.onConnect(); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onConnect(adapter); } } public void onMessage(byte[] data) { super.onMessage(data); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onMessage(adapter, data); } } public void onMessage(String text) { super.onMessage(text); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onMessage(adapter, text); } } public void onPing(DataFrame frame) { super.onPing(frame); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onPing(adapter, frame.getBytes()); } } public void onPong(DataFrame frame) { super.onPong(frame); - for (OperationMessageListener listener : listeners) { + for (OperationMessageListener listener : messageListeners) { listener.onPong(adapter, frame.getBytes()); } } diff --git a/OpenICF-java-framework/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java b/OpenICF-java-framework/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java index 5508b4685..fb42058cb 100755 --- a/OpenICF-java-framework/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java +++ b/OpenICF-java-framework/testbundlev1/src/main/java/org/identityconnectors/testconnector/TstAbstractConnector.java @@ -20,6 +20,7 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.testconnector; @@ -425,7 +426,7 @@ public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler h // Paged Search final String pagedResultsCookie = options.getPagedResultsCookie(); String currentPagedResultsCookie = options.getPagedResultsCookie(); - final Integer pagedResultsOffset = + final int pagedResultsOffset = null != options.getPagedResultsOffset() ? Math.max(0, options .getPagedResultsOffset()) : 0; final Integer pageSize = options.getPageSize(); @@ -435,7 +436,8 @@ public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler h int handled = 0; for (ConnectorObject entry : resultSet) { - if (pageStartIndex < 0 && pagedResultsCookie.equals(entry.getName().getNameValue())) { + if (pageStartIndex < 0 && null != pagedResultsCookie + && pagedResultsCookie.equals(entry.getName().getNameValue())) { pageStartIndex = index + 1; } diff --git a/OpenICF-ldap-connector/src/main/java/org/identityconnectors/ldap/ADLdapUtil.java b/OpenICF-ldap-connector/src/main/java/org/identityconnectors/ldap/ADLdapUtil.java index 0aeb6e2cd..5956fa693 100644 --- a/OpenICF-ldap-connector/src/main/java/org/identityconnectors/ldap/ADLdapUtil.java +++ b/OpenICF-ldap-connector/src/main/java/org/identityconnectors/ldap/ADLdapUtil.java @@ -20,6 +20,7 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.ldap; @@ -77,7 +78,9 @@ public static String objectGUIDtoDashedString(Attribute attr){ throw new ConnectorException(LdapConstants.MS_GUID_ATTR+" attribute has the wrong length ("+GUID.length+"). Should be 16 bytes."); } } - catch(NamingException e){} + catch(NamingException e){ + throw new ConnectorException("Error reading " + attr.getID() + " attribute", e); + } StringBuilder sGUID = new StringBuilder(43); sGUID.append(" 0 && i < length) { + if (i > 0) { sb.append(",\n"); } Object item = Array.get(property.getValue(), i); diff --git a/OpenICF-maven-plugin/src/main/java/org/forgerock/openicf/maven/DocBookResourceMojo.java b/OpenICF-maven-plugin/src/main/java/org/forgerock/openicf/maven/DocBookResourceMojo.java index 4e258c802..74c9bc0b0 100644 --- a/OpenICF-maven-plugin/src/main/java/org/forgerock/openicf/maven/DocBookResourceMojo.java +++ b/OpenICF-maven-plugin/src/main/java/org/forgerock/openicf/maven/DocBookResourceMojo.java @@ -496,9 +496,9 @@ public boolean accept(File pathname) { FileUtils.mkdir(f.getParentFile().getAbsolutePath()); - Writer writer = new FileWriter(f); - - w.write(writer, remoteResourcesBundle); + try (Writer writer = new FileWriter(f)) { + w.write(writer, remoteResourcesBundle); + } } catch (IOException e) { throw new MojoExecutionException("Error creating remote resources manifest.", e); } diff --git a/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/XMLFilterTranslator.java b/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/XMLFilterTranslator.java index b2a02eb21..deff517de 100644 --- a/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/XMLFilterTranslator.java +++ b/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/XMLFilterTranslator.java @@ -112,9 +112,7 @@ public Query createContainsAllValuesExpression(ContainsAllValuesFilter filter, b for (int i = 1; i < numOfValues; i++) { Query rightSide = equalsQueries.get(i); orQuery = createOrExpression(leftSide, rightSide); - if (i != numOfValues) { - leftSide = orQuery; - } + leftSide = orQuery; } return orQuery; diff --git a/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/util/AttributeTypeUtil.java b/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/util/AttributeTypeUtil.java index b6bc1296b..d397ade0c 100644 --- a/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/util/AttributeTypeUtil.java +++ b/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/util/AttributeTypeUtil.java @@ -20,6 +20,7 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted 2010 [name of copyright owner]" + * Portions Copyrighted 2026 3A Systems, LLC * * $Id$ */ @@ -126,7 +127,7 @@ public static List findAttributeValue(Attribute attr, AttributeInfo attr List results = new ArrayList(); String stringValue = null; - if (attr != null && attr.getValue() != null && attrInfo != null) { + if (attr != null && attr.getValue() != null) { if (attrInfo.getType().isPrimitive()) javaClass = convertPrimitiveToWrapper(attrInfo.getType().getName()); diff --git a/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/xsdparser/SchemaParser.java b/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/xsdparser/SchemaParser.java index 9e7caf894..a568aef7b 100644 --- a/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/xsdparser/SchemaParser.java +++ b/OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/xsdparser/SchemaParser.java @@ -20,6 +20,7 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted 2010 [name of copyright owner]" + * Portions Copyrighted 2026 3A Systems, LLC * * $Id$ */ @@ -96,6 +97,15 @@ public Schema parseSchema() { while (typesIterator.hasNext()) { XSElementDecl type = schema.getElementDecl(typesIterator.next()); + if (type == null) { + continue; + } + XSComplexType xsCompType = type.getType().asComplexType(); + if (xsCompType == null) { + log.warn("Element {0} has a simple type, it is not an object class: skipped", + type.getName()); + continue; + } Set attributes = new HashSet(); List> supportedOp = new LinkedList>(); @@ -103,80 +113,76 @@ public Schema parseSchema() { ObjectClassInfoBuilder objectClassBuilder = new ObjectClassInfoBuilder(); objectClassBuilder.setType(type.getName()); - if (type != null) { - XSComplexType xsCompType = type.getType().asComplexType(); - - if (xsCompType.getAnnotation() != null) { - String supportedOpString = xsCompType.getAnnotation().getAnnotation().toString(); - String[] supportedOpStringSplit = supportedOpString.split(" |\n"); - List supportedOpListString = Arrays.asList(supportedOpStringSplit); - - supportedOp = SchemaParserUtil.getSupportedOpClasses(supportedOpListString); - } + if (xsCompType.getAnnotation() != null) { + String supportedOpString = xsCompType.getAnnotation().getAnnotation().toString(); + String[] supportedOpStringSplit = supportedOpString.split(" |\n"); + List supportedOpListString = Arrays.asList(supportedOpStringSplit); - XSContentType xsContType = xsCompType.getContentType(); - XSParticle particle = xsContType.asParticle(); + supportedOp = SchemaParserUtil.getSupportedOpClasses(supportedOpListString); + } - if (particle != null) { - XSTerm xsTerm = particle.getTerm(); + XSContentType xsContType = xsCompType.getContentType(); + XSParticle particle = xsContType.asParticle(); - if (xsTerm.isModelGroup()) { - XSModelGroup xsModelGroup = xsTerm.asModelGroup(); - XSParticle[] particles = xsModelGroup.getChildren(); + if (particle != null) { + XSTerm xsTerm = particle.getTerm(); - for (XSParticle childParticle : particles) { - XSTerm childParticleTerm = childParticle.getTerm(); + if (xsTerm.isModelGroup()) { + XSModelGroup xsModelGroup = xsTerm.asModelGroup(); + XSParticle[] particles = xsModelGroup.getChildren(); - if (childParticleTerm.isElementDecl()) { - XSElementDecl childElementTerm = childParticleTerm.asElementDecl(); - Set flags = new HashSet(); - Class attributeClassType = null; + for (XSParticle childParticle : particles) { + XSTerm childParticleTerm = childParticle.getTerm(); - if (childParticle.getMinOccurs() == 1) { - flags.add(Flags.REQUIRED); - } + if (childParticleTerm.isElementDecl()) { + XSElementDecl childElementTerm = childParticleTerm.asElementDecl(); + Set flags = new HashSet(); + Class attributeClassType = null; - if (childParticle.getMaxOccurs() > 1 || childParticle.getMaxOccurs() == -1) { - flags.add(Flags.MULTIVALUED); - } + if (childParticle.getMinOccurs() == 1) { + flags.add(Flags.REQUIRED); + } - if (childElementTerm.getAnnotation() != null) { - XSAnnotation childElementTermAnnotaion = childElementTerm.getAnnotation(); - String annotations = childElementTermAnnotaion.getAnnotation().toString(); + if (childParticle.getMaxOccurs() > 1 || childParticle.getMaxOccurs() == -1) { + flags.add(Flags.MULTIVALUED); + } - String[] annotationsSplit = annotations.split(" |\n"); - List annotationList = Arrays.asList(annotationsSplit); + if (childElementTerm.getAnnotation() != null) { + XSAnnotation childElementTermAnnotaion = childElementTerm.getAnnotation(); + String annotations = childElementTermAnnotaion.getAnnotation().toString(); - Set flagList = SchemaParserUtil.getFlags(annotationList); + String[] annotationsSplit = annotations.split(" |\n"); + List annotationList = Arrays.asList(annotationsSplit); - if (flagList != null) { - flags.addAll(flagList); - } - - attributeClassType = SchemaParserUtil.getJavaClassType(annotationList); + Set flagList = SchemaParserUtil.getFlags(annotationList); + if (flagList != null) { + flags.addAll(flagList); } + + attributeClassType = SchemaParserUtil.getJavaClassType(annotationList); + + } - if (attributeClassType == null) { - XSType typeNotFlagedJavaclass = childElementTerm.getType(); - - if(typeNotFlagedJavaclass.getName() != null){ - attributeClassType = SchemaParserUtil.findJavaClassType(typeNotFlagedJavaclass.getName()); - } + if (attributeClassType == null) { + XSType typeNotFlagedJavaclass = childElementTerm.getType(); + + if(typeNotFlagedJavaclass.getName() != null){ + attributeClassType = SchemaParserUtil.findJavaClassType(typeNotFlagedJavaclass.getName()); } + } - AttributeInfo attributeInfo = null; + AttributeInfo attributeInfo = null; - if (attributeClassType != null) { - attributeInfo = AttributeInfoBuilder.build(childElementTerm.getName(), attributeClassType, flags); - } - else { - attributeInfo = AttributeInfoBuilder.build(childElementTerm.getName()); - } + if (attributeClassType != null) { + attributeInfo = AttributeInfoBuilder.build(childElementTerm.getName(), attributeClassType, flags); + } + else { + attributeInfo = AttributeInfoBuilder.build(childElementTerm.getName()); + } - if(attributeInfo != null){ - attributes.add(attributeInfo); - } + if(attributeInfo != null){ + attributes.add(attributeInfo); } } } diff --git a/OpenICF-xml-connector/src/test/java/org/forgerock/openicf/connectors/xml/SchemaParserTests.java b/OpenICF-xml-connector/src/test/java/org/forgerock/openicf/connectors/xml/SchemaParserTests.java index 413c292bd..c6e643c43 100644 --- a/OpenICF-xml-connector/src/test/java/org/forgerock/openicf/connectors/xml/SchemaParserTests.java +++ b/OpenICF-xml-connector/src/test/java/org/forgerock/openicf/connectors/xml/SchemaParserTests.java @@ -20,12 +20,18 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted 2010 [name of copyright owner]" + * Portions Copyrighted 2026 3A Systems, LLC * * $Id$ */ package org.forgerock.openicf.connectors.xml; import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import org.identityconnectors.common.IOUtil; +import org.identityconnectors.framework.common.objects.Schema; import org.testng.annotations.Test; import org.testng.annotations.BeforeMethod; import org.testng.AssertJUnit; @@ -71,4 +77,28 @@ public void getXsdSchemaShouldReturnXsdSchemaSet() { public void parseSchemaShouldReturnSchema() { AssertJUnit.assertNotNull(parser.parseSchema()); } + + @Test + public void parseSchemaShouldSkipSimpleTypedElement() throws IOException { + final File dir = Files.createTempDirectory("xsd").toFile(); + try { + // the schema imports its neighbour by a relative location + Files.copy(XmlConnectorTestUtil.ICF_SCHEMA_FILEPATH.toPath(), + new File(dir, XmlConnectorTestUtil.ICF_SCHEMA_FILEPATH.getName()).toPath()); + final String xsd = new String(Files.readAllBytes( + XmlConnectorTestUtil.XSD_SCHEMA_FILEPATH.toPath()), StandardCharsets.UTF_8); + AssertJUnit.assertTrue(xsd.contains("")); + final File withSimpleType = new File(dir, "with-simple-type.xsd"); + Files.write(withSimpleType.toPath(), xsd.replace("", + "\n") + .getBytes(StandardCharsets.UTF_8)); + + final Schema schema = new SchemaParser(XMLConnector.class, withSimpleType).parseSchema(); + + AssertJUnit.assertNull(schema.findObjectClassInfo("note")); + AssertJUnit.assertEquals(parser.parseSchema(), schema); + } finally { + IOUtil.delete(dir); + } + } }