.github/workflows/build.yml
@@ -24,8 +24,9 @@ sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/$(lsb_release -c -s)/winehq-$(lsb_release -c -s).sources sudo apt-get update sudo apt install --install-recommends winehq-stable || sudo apt install --install-recommends winehq-staging sudo mkdir -p /opt/wine/mono && sudo wget "https://dl.winehq.org/wine/wine-mono/8.0.0/wine-mono-8.0.0-x86.tar.xz" -P /opt/wine/mono && sudo tar -xf /opt/wine/mono/wine-mono-8.0.0-x86.tar.xz -C /opt/wine/mono && sudo rm /opt/wine/mono/wine-mono-8.0.0-x86.tar.xz wine --version version="9.4.0"; sudo wget "https://dl.winehq.org/wine/wine-mono/$version/wine-mono-$version-x86.msi" -O /tmp/wine-mono.msi wine msiexec /i /tmp/wine-mono.msi - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -41,11 +42,6 @@ path: ~/.m2/repository key: ${{ runner.os }}-m2-repository-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2-repository - name: Run docker cassandra if: runner.os == 'Linux' run: | docker run --rm -it -d -p 9042:9042 --name cassandra cassandra timeout 5m bash -c 'until docker logs cassandra | grep -q "Created default superuser role"; do sleep 5; done' - name: Set Integration Test Environment id: failsafe if: runner.os != 'Windows' @@ -71,17 +67,37 @@ opendj-server-legacy/target/package/opendj/bin/start-ds opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example2,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000 opendj-server-legacy/target/package/opendj/bin/stop-ds rm -rf opendj-server-legacy/target/package/opendj/config opendj-server-legacy/target/package/opendj/db opendj-server-legacy/target/package/opendj/changelogDb opendj-server-legacy/target/package/opendj/logs rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs} - name: Test LDAP in Cassandra if: runner.os == 'Linux' run: | docker run --rm -it -d -p 9042:9042 --name cassandra cassandra timeout 5m bash -c 'until docker logs cassandra | grep -q "Created default superuser role"; do sleep 5; done' export OPENDJ_JAVA_ARGS="-server -Xmx512m -Ddatastax-java-driver.basic.contact-points.0=localhost:9042 -Ddatastax-java-driver.basic.load-balancing-policy.local-datacenter=datacenter1" opendj-server-legacy/target/package/opendj/setup --backendType cas -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --cli --acceptLicense --no-prompt opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=userRoot --type cas --set base-dn:dc=example,dc=com --set db-directory:keyspace_name --set enabled:true --no-prompt --trustAll opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template opendj-server-legacy/target/package/opendj/bin/import-ldif --ldifFile /tmp/test.ldif --backendID=userRoot -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --trustAll opendj-server-legacy/target/package/opendj/bin/status --bindDN "cn=Directory Manager" --bindPassword password opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000 opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000 opendj-server-legacy/target/package/opendj/bin/stop-ds rm -rf opendj-server-legacy/target/package/opendj/config opendj-server-legacy/target/package/opendj/db opendj-server-legacy/target/package/opendj/changelogDb opendj-server-legacy/target/package/opendj/logs rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs} - name: Test LDAP in Postgres if: runner.os == 'Linux' run: | docker run --rm -it -d -p 5432:5432 -e POSTGRES_DB=database_name -e POSTGRES_PASSWORD=password --name postgres postgres timeout 5m bash -c 'until docker logs postgres | grep -q "database system is ready to accept connections"; do sleep 5; done' export OPENDJ_JAVA_ARGS="-server -Xmx512m" opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --cli --acceptLicense --no-prompt opendj-server-legacy/target/package/opendj/bin/dsconfig create-backend -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=userRoot --type jdbc --set base-dn:dc=example,dc=com --set db-directory:jdbc:postgresql://localhost:5432/database_name?user=postgres\&password=password --set enabled:true --no-prompt --trustAll opendj-server-legacy/target/package/opendj/bin/makeldif -o /tmp/test.ldif -c suffix=dc=example,dc=com opendj-server-legacy/target/package/opendj/config/MakeLDIF/example.template opendj-server-legacy/target/package/opendj/bin/import-ldif --ldifFile /tmp/test.ldif --backendID=userRoot -h localhost -p 4444 --bindDN "cn=Directory Manager" --bindPassword password --trustAll opendj-server-legacy/target/package/opendj/bin/status --bindDN "cn=Directory Manager" --bindPassword password opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 10000 opendj-server-legacy/target/package/opendj/bin/stop-ds rm -rf opendj-server-legacy/target/package/opendj/{config,db,changelogDb,logs} - name: Test on Windows if: runner.os == 'Windows' run: | .github/workflows/deploy.yml
@@ -24,8 +24,9 @@ sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/$(lsb_release -c -s)/winehq-$(lsb_release -c -s).sources sudo apt-get update sudo apt install --install-recommends winehq-stable || sudo apt install --install-recommends winehq-staging sudo mkdir -p /opt/wine/mono && sudo wget "https://dl.winehq.org/wine/wine-mono/8.0.0/wine-mono-8.0.0-x86.tar.xz" -P /opt/wine/mono && sudo tar -xf /opt/wine/mono/wine-mono-8.0.0-x86.tar.xz -C /opt/wine/mono && sudo rm /opt/wine/mono/wine-mono-8.0.0-x86.tar.xz wine --version version="9.4.0"; sudo wget "https://dl.winehq.org/wine/wine-mono/$version/wine-mono-$version-x86.msi" -O /tmp/wine-mono.msi wine msiexec /i /tmp/wine-mono.msi - uses: actions/checkout@v4 with: fetch-depth: 0 .github/workflows/release.yml
@@ -28,8 +28,9 @@ sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/$(lsb_release -c -s)/winehq-$(lsb_release -c -s).sources sudo apt-get update sudo apt install --install-recommends winehq-stable || sudo apt install --install-recommends winehq-staging sudo mkdir -p /opt/wine/mono && sudo wget "https://dl.winehq.org/wine/wine-mono/8.0.0/wine-mono-8.0.0-x86.tar.xz" -P /opt/wine/mono && sudo tar -xf /opt/wine/mono/wine-mono-8.0.0-x86.tar.xz -C /opt/wine/mono && sudo rm /opt/wine/mono/wine-mono-8.0.0-x86.tar.xz wine --version version="9.4.0"; sudo wget "https://dl.winehq.org/wine/wine-mono/$version/wine-mono-$version-x86.msi" -O /tmp/wine-mono.msi wine msiexec /i /tmp/wine-mono.msi - uses: actions/checkout@v4 with: fetch-depth: 0 README.md
@@ -13,7 +13,7 @@ OpenDJ is an [LDAPv3](http://tools.ietf.org/html/rfc4510) compliant directory service, which has been developed for the Java platform, providing a high performance, highly available, and secure store for the identities managed by your organization. Its easy installation process, combined with the power of the Java platform makes OpenDJ the simplest, fastest directory to deploy and manage and allow [store LDAPv3 database in Cassandra/Scylla cluster](https://github.com/OpenIdentityPlatform/OpenDJ/wiki/How-To#store-ldap-catalog-data-in-cassandra-nosql-cluster). the simplest, fastest directory to deploy and manage and allow store LDAPv3 database in [SQL JDBC database](https://github.com/OpenIdentityPlatform/OpenDJ/wiki/How-To#store-ldap-catalog-data-in-jdbc-databse) or [NoSQL Cassandra/Scylla cluster](https://github.com/OpenIdentityPlatform/OpenDJ/wiki/How-To#store-ldap-catalog-data-in-cassandra-nosql-cluster). An open source, lightweight, embeddable directory that can easily share real-time customer, device, and user identity data across enterprise, cloud, social, and mobile environments. * Massive data scale and high availability provide developers with ultra-lightweight ways to access identity data opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/JDBCBackendConfiguration.xml
New file @@ -0,0 +1,74 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- 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 2024 3A Systems LLC ! --> <adm:managed-object name="jdbc-backend" plural-name="jdbc-backends" package="org.forgerock.opendj.server.config" extends="pluggable-backend" xmlns:adm="http://opendj.forgerock.org/admin" xmlns:ldap="http://opendj.forgerock.org/admin-ldap" xmlns:cli="http://opendj.forgerock.org/admin-cli"> <adm:synopsis> A <adm:user-friendly-name/> stores application data in JDBC source. </adm:synopsis> <adm:description> It is the traditional "directory server" backend and is similar to the backends provided by the Sun Java System Directory Server. The <adm:user-friendly-name /> stores the entries in an encoded form and also provides indexes that can be used to quickly locate target entries based on different kinds of criteria. </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-jdbc-backend</ldap:name> <ldap:superior>ds-cfg-pluggable-backend</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-class" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value> org.opends.server.backends.jdbc.Backend </adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="db-directory" mandatory="true"> <adm:TODO>Default this to the db/backend-id</adm:TODO> <adm:synopsis> Specifies the connection string </adm:synopsis> <adm:description> jdbc:postgresql://localhost/test </adm:description> <adm:requires-admin-action> <adm:component-restart /> </adm:requires-admin-action> <adm:default-behavior> <adm:defined> <adm:value>jdbc:postgresql://localhost/test</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-db-directory</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opendj-server-legacy/pom.xml
@@ -259,6 +259,11 @@ </exclusions> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.4</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <scope>test</scope> @@ -267,7 +272,13 @@ <dependency> <groupId>org.testcontainers</groupId> <artifactId>cassandra</artifactId> <version>1.19.0</version> <version>1.20.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.20.4</version> <scope>test</scope> </dependency> </dependencies> @@ -1082,24 +1093,6 @@ </executions> </plugin> <!-- Build javadoc --> <plugin> <groupId>org.openidentityplatform.maven.plugins</groupId> <artifactId>javadoc-updater-maven-plugin</artifactId> <version>1.0.0</version> <executions> <execution> <phase>site</phase> <goals> <goal>fixjavadoc</goal> </goals> <configuration> <directory>${project.build.directory}/site/javadoc</directory> </configuration> </execution> </executions> </plugin> <!-- Release project --> <plugin> <groupId>org.apache.maven.plugins</groupId> opendj-server-legacy/resource/schema/02-config.ldif
@@ -15,7 +15,7 @@ # Portions Copyright 2011 profiq, s.r.o. # Portions Copyright 2012 Manuel Gaupp # Portions copyright 2015 Edan Idzerda # Portions copyright 2023 3A Systems LLC # Portions copyright 2023-2024 3A Systems LLC # This file contains the attribute type and objectclass definitions for use # with the Directory Server configuration. @@ -6013,6 +6013,12 @@ STRUCTURAL MUST ds-cfg-db-directory X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.60142.2.1.2.2 NAME 'ds-cfg-jdbc-backend' SUP ds-cfg-pluggable-backend STRUCTURAL MUST ds-cfg-db-directory X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.27 NAME 'ds-task-reset-change-number' SUP ds-task opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Backend.java
New file @@ -0,0 +1,31 @@ /* * 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 2024 3A Systems, LLC. */ package org.opends.server.backends.jdbc; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.server.config.server.JDBCBackendCfg; import org.opends.server.backends.pluggable.BackendImpl; import org.opends.server.core.ServerContext; public class Backend extends BackendImpl<JDBCBackendCfg>{ @Override protected Storage configureStorage(JDBCBackendCfg cfg, ServerContext serverContext) throws ConfigException { return new Storage(cfg, serverContext); } } opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java
New file @@ -0,0 +1,545 @@ /* * 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 2024 3A Systems, LLC. */ package org.opends.server.backends.jdbc; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.config.server.ConfigurationChangeListener; import org.forgerock.opendj.ldap.ByteSequence; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.server.config.server.JDBCBackendCfg; import org.opends.server.backends.pluggable.spi.*; import org.opends.server.core.ServerContext; import org.opends.server.types.BackupConfig; import org.opends.server.types.BackupDirectory; import org.opends.server.types.DirectoryException; import org.opends.server.types.RestoreConfig; import org.opends.server.util.BackupManager; import java.sql.*; import java.util.*; import static org.opends.server.backends.pluggable.spi.StorageUtils.addErrorMessage; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; public class Storage implements org.opends.server.backends.pluggable.spi.Storage, ConfigurationChangeListener<JDBCBackendCfg>{ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private JDBCBackendCfg config; public Storage(JDBCBackendCfg cfg, ServerContext serverContext) { this.config = cfg; cfg.addJDBCChangeListener(this); } //config @Override public boolean isConfigurationChangeAcceptable(JDBCBackendCfg configuration,List<LocalizableMessage> unacceptableReasons) { return true; } @Override public ConfigChangeResult applyConfigurationChange(JDBCBackendCfg cfg) { final ConfigChangeResult ccr = new ConfigChangeResult(); try { this.config = cfg; } catch (Exception e) { addErrorMessage(ccr, LocalizableMessage.raw(stackTraceToSingleLineString(e))); } return ccr; } ResultSet executeResultSet(PreparedStatement statement) throws SQLException { if (logger.isTraceEnabled()) { logger.trace(LocalizableMessage.raw("jdbc: %s",statement)); } return statement.executeQuery(); } boolean execute(PreparedStatement statement) throws SQLException { if (logger.isTraceEnabled()) { logger.trace(LocalizableMessage.raw("jdbc: %s",statement)); } return statement.execute(); } Connection con; @Override public void open(AccessMode accessMode) throws Exception { con=DriverManager.getConnection(config.getDBDirectory()); con.setAutoCommit(false); con.setReadOnly(!AccessMode.READ_WRITE.equals(accessMode)); storageStatus = StorageStatus.working(); } private StorageStatus storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed")); @Override public StorageStatus getStorageStatus() { return storageStatus; } @Override public void close() { storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed")); try { if (con != null && !con.isClosed()) { con.close(); } } catch (SQLException e) { logger.error(LocalizableMessage.raw("close(): %s",e),e); } con=null; } String getTableName(TreeName treeName) { return "\"OpenDJ"+treeName.toString()+"\""; } @Override public void removeStorageFiles() throws StorageRuntimeException { final boolean isOpen=getStorageStatus().isWorking(); if (!isOpen) { try { open(AccessMode.READ_WRITE); }catch (Exception e) { throw new StorageRuntimeException(e); } } try { for (TreeName treeName : listTrees()) { final PreparedStatement statement=con.prepareStatement("drop table "+getTableName(treeName)); execute(statement); } }catch (Throwable e) { throw new StorageRuntimeException(e); } if (!isOpen) { close(); } } //operation @Override public <T> T read(ReadOperation<T> readOperation) throws Exception { return readOperation.run(new ReadableTransactionImpl()); } @Override public void write(WriteOperation writeOperation) throws Exception { try { writeOperation.run(new WriteableTransactionTransactionImpl()); con.commit(); } catch (Exception e) { try { con.rollback(); } catch (SQLException ex) {} throw e; } } private class ReadableTransactionImpl implements ReadableTransaction { @Override public ByteString read(TreeName treeName, ByteSequence key) { try { final PreparedStatement statement=con.prepareStatement("select v from "+getTableName(treeName)+" where k=?"); statement.setBytes(1,key.toByteArray()); try(ResultSet rc=executeResultSet(statement)) { return rc.next() ? ByteString.wrap(rc.getBytes("v")) : null; } }catch (SQLException e) { throw new RuntimeException(e); } } @Override public Cursor<ByteString, ByteString> openCursor(TreeName treeName) { return new CursorImpl(treeName); } @Override public long getRecordCount(TreeName treeName) { try { final PreparedStatement statement=con.prepareStatement("select count(*) from "+getTableName(treeName)); try(ResultSet rc=executeResultSet(statement)) { return rc.next() ? rc.getLong(1) : 0; } }catch (SQLException e) { throw new RuntimeException(e); } } } private final class WriteableTransactionTransactionImpl extends ReadableTransactionImpl implements WriteableTransaction { public WriteableTransactionTransactionImpl() { super(); try { if (con.isReadOnly()) { throw new ReadOnlyStorageException(); } } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void openTree(TreeName treeName, boolean createOnDemand) { if (createOnDemand) { try { final PreparedStatement statement=con.prepareStatement("create table if not exists "+getTableName(treeName)+" (k bytea primary key,v bytea)"); execute(statement); }catch (SQLException e) { throw new RuntimeException(e); } } } public void clearTree(TreeName treeName) { try { final PreparedStatement statement=con.prepareStatement("truncate table "+getTableName(treeName)); execute(statement); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public void deleteTree(TreeName treeName) { try { final PreparedStatement statement=con.prepareStatement("drop table "+getTableName(treeName)); execute(statement); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public void put(TreeName treeName, ByteSequence key, ByteSequence value) { try { delete(treeName,key); final PreparedStatement statement=con.prepareStatement("insert into "+getTableName(treeName)+" (k,v) values(?,?) "); statement.setBytes(1,key.toByteArray()); statement.setBytes(2,value.toByteArray()); execute(statement); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean update(TreeName treeName, ByteSequence key, UpdateFunction f) { final ByteString oldValue=read(treeName,key); final ByteSequence newValue=f.computeNewValue(oldValue); if (Objects.equals(newValue, oldValue)) { return false; } if (newValue == null) { delete(treeName, key); return true; } put(treeName,key,newValue); return true; } @Override public boolean delete(TreeName treeName, ByteSequence key) { try { final PreparedStatement statement=con.prepareStatement("delete from "+getTableName(treeName)+" where k=?"); statement.setBytes(1,key.toByteArray()); execute(statement); }catch (SQLException e) { throw new RuntimeException(e); } return true; } } private final class CursorImpl implements Cursor<ByteString, ByteString> { final TreeName treeName; //final WriteableTransactionTransactionImpl tx; ResultSet rc; public CursorImpl(TreeName treeName) { this.treeName=treeName; //this.tx=tx; try { final PreparedStatement statement=con.prepareStatement("select k,v from "+getTableName(treeName)+" order by k", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); rc=executeResultSet(statement); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean next() { try { return rc.next(); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean isDefined() { try{ return rc.getRow()>0; }catch (SQLException e) { throw new RuntimeException(e); } } @Override public ByteString getKey() throws NoSuchElementException { if (!isDefined()) { throw new NoSuchElementException(); } try{ return ByteString.wrap(rc.getBytes("k")); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public ByteString getValue() throws NoSuchElementException { if (!isDefined()) { throw new NoSuchElementException(); } try{ return ByteString.wrap(rc.getBytes("v")); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public void delete() throws NoSuchElementException, UnsupportedOperationException { if (!isDefined()) { throw new NoSuchElementException(); } try{ rc.deleteRow(); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public void close() { if (rc!=null) { try{ rc.close(); }catch (SQLException e) { throw new RuntimeException(e); } rc = null; } } @Override public boolean positionToKeyOrNext(ByteSequence key) { if (!isDefined() || key.compareTo(getKey())<0) { //restart iterator try{ rc.first(); }catch (SQLException e) { throw new RuntimeException(e); } } try{ if (!isDefined()){ return false; } do { if (key.compareTo(getKey())<=0) { return true; } }while(rc.next()); }catch (SQLException e) { throw new RuntimeException(e); } return false; } @Override public boolean positionToKey(ByteSequence key) { if (!isDefined() || key.compareTo(getKey())<0) { //restart iterator try{ rc.first(); }catch (SQLException e) { throw new RuntimeException(e); } } if (!isDefined()){ return false; } if (isDefined() && key.compareTo(getKey())==0) { return true; } try{ do { if (key.compareTo(getKey())==0) { return true; } }while(rc.next()); }catch (SQLException e) { throw new RuntimeException(e); } return false; } @Override public boolean positionToLastKey() { try{ return rc.last(); }catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean positionToIndex(int index) { try{ rc.first(); }catch (SQLException e) { throw new RuntimeException(e); } if (!isDefined()){ return false; } int ct=0; try{ do { if (ct==index) { return true; } ct++; }while(rc.next()); }catch (SQLException e) { throw new RuntimeException(e); } return false; } } @Override public Set<TreeName> listTrees() { final Set<TreeName> res=new HashSet<>(); try(ResultSet rs = con.getMetaData().getTables(null, null, "OpenDJ%", new String[]{"TABLE"})) { while (rs.next()) { res.add(TreeName.valueOf(rs.getString("TABLE_NAME").substring(6))); } } catch (SQLException e) { throw new RuntimeException(e); } return res; } private final class ImporterImpl implements Importer { final WriteableTransactionTransactionImpl tx; final Boolean isOpen; public ImporterImpl() { isOpen=getStorageStatus().isWorking(); if (!isOpen) { try { open(AccessMode.READ_WRITE); }catch (Exception e) { throw new StorageRuntimeException(e); } } tx=new WriteableTransactionTransactionImpl(); } @Override public void close() { try { con.commit(); } catch (SQLException e) { throw new RuntimeException(e); } if (!isOpen) { Storage.this.close(); } } @Override public void clearTree(TreeName name) { tx.clearTree(name); } @Override public void put(TreeName treeName, ByteSequence key, ByteSequence value) { tx.put(treeName, key, value); } @Override public ByteString read(TreeName treeName, ByteSequence key) { return tx.read(treeName, key); } @Override public SequentialCursor<ByteString, ByteString> openCursor(TreeName treeName) { return tx.openCursor(treeName); } } //import @Override public Importer startImport() throws ConfigException, StorageRuntimeException { return new ImporterImpl(); } //backup @Override public boolean supportsBackupAndRestore() { return true; } @Override public void createBackup(BackupConfig backupConfig) throws DirectoryException { // TODO backup over snapshot or SQL export //new BackupManager(config.getBackendId()).createBackup(this, backupConfig); } @Override public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException { new BackupManager(config.getBackendId()).removeBackup(backupDirectory, backupID); } @Override public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException { // TODO restore over snapshot or SQL export //new BackupManager(config.getBackendId()).restoreBackup(this, restoreConfig); } } opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/package-info.java
New file @@ -0,0 +1,18 @@ /* * 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 2024 3A Systems LLC. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.PRIVATE) package org.opends.server.backends.jdbc; opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/EncryptedTestCase.java
New file @@ -0,0 +1,81 @@ /* * 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 2023-2024 3A Systems, LLC. */ package org.opends.server.backends.jdbc; import org.forgerock.opendj.server.config.server.JDBCBackendCfg; import org.opends.server.backends.pluggable.PluggableBackendImplTestCase; import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.PostgreSQLContainer; import org.testng.SkipException; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.sql.Connection; import java.sql.DriverManager; import static org.forgerock.opendj.config.ConfigurationMock.mockCfg; import static org.mockito.Mockito.when; @Test public class EncryptedTestCase extends PluggableBackendImplTestCase<JDBCBackendCfg> { PostgreSQLContainer container; @BeforeClass @Override public void setUp() throws Exception { if(DockerClientFactory.instance().isDockerAvailable()) { container = new PostgreSQLContainer<>("postgres:latest") .withExposedPorts(5432) .withUsername("postgres") .withPassword("password") .withDatabaseName("database_name"); container.start(); } try(Connection con= DriverManager.getConnection(createBackendCfg().getDBDirectory())){ } catch (Exception e) { throw new SkipException("run before test: docker run --rm -it -p 5432:5432 -e POSTGRES_DB=database_name -e POSTGRES_PASSWORD=password --name postgres postgres"); } super.setUp(); } @Override protected Backend createBackend() { return new Backend(); } @Override protected JDBCBackendCfg createBackendCfg() { JDBCBackendCfg backendCfg = mockCfg(JDBCBackendCfg.class); when(backendCfg.getBackendId()).thenReturn("EncPsqlTestCase"+System.currentTimeMillis()); when(backendCfg.getDBDirectory()).thenReturn("jdbc:postgresql://localhost:"+ ((container==null)?"5432":container.getMappedPort(5432))+"/database_name?user=postgres&password=password"); when(backendCfg.isConfidentialityEnabled()).thenReturn(true); when(backendCfg.getCipherKeyLength()).thenReturn(128); when(backendCfg.getCipherTransformation()).thenReturn("AES/CBC/PKCS5Padding"); return backendCfg; } @AfterClass @Override public void cleanUp() throws Exception { super.cleanUp(); if(container != null) { container.close(); } } } opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/TestCase.java
New file @@ -0,0 +1,79 @@ /* * 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 2024 3A Systems, LLC. */ package org.opends.server.backends.jdbc; import org.forgerock.opendj.server.config.server.JDBCBackendCfg; import org.opends.server.backends.pluggable.PluggableBackendImplTestCase; import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.PostgreSQLContainer; import org.testng.SkipException; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.sql.Connection; import java.sql.DriverManager; import static org.forgerock.opendj.config.ConfigurationMock.mockCfg; import static org.mockito.Mockito.when; //docker run --rm -it -p 5432:5432 -e POSTGRES_PASSWORD=password --name postgres postgres @Test public class TestCase extends PluggableBackendImplTestCase<JDBCBackendCfg> { PostgreSQLContainer container; @BeforeClass @Override public void setUp() throws Exception { if(DockerClientFactory.instance().isDockerAvailable()) { container = new PostgreSQLContainer<>("postgres:latest") .withExposedPorts(5432) .withUsername("postgres") .withPassword("password") .withDatabaseName("database_name"); container.start(); } try(Connection con= DriverManager.getConnection(createBackendCfg().getDBDirectory())){ } catch (Exception e) { throw new SkipException("run before test: docker run --rm -it -p 5432:5432 -e POSTGRES_DB=database_name -e POSTGRES_PASSWORD=password --name postgres postgres"); } super.setUp(); } @Override protected Backend createBackend() { return new Backend(); } @Override protected JDBCBackendCfg createBackendCfg() { JDBCBackendCfg backendCfg = mockCfg(JDBCBackendCfg.class); when(backendCfg.getBackendId()).thenReturn("PsqlTestCase"); when(backendCfg.getDBDirectory()).thenReturn("jdbc:postgresql://localhost:"+ ((container==null)?"5432":container.getMappedPort(5432))+"/database_name?user=postgres&password=password"); return backendCfg; } @AfterClass @Override public void cleanUp() throws Exception { super.cleanUp(); if(container != null) { container.close(); } } } opendj-server-legacy/src/test/java/org/opends/server/replication/GenerationIdTest.java
@@ -13,6 +13,7 @@ * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2016 ForgeRock AS. * Portions Copyright 2023-2024 3A Systems, LLC. */ package org.opends.server.replication; @@ -990,7 +991,7 @@ private void waitForStableGenerationId(final long expectedGenId) throws Exception { TestTimer timer = new TestTimer.Builder() .maxSleep(20, SECONDS) .maxSleep(30, SECONDS) .sleepTimes(100, MILLISECONDS) .toTimer(); timer.repeatUntilSuccess(new CallableVoid() pom.xml
@@ -286,22 +286,6 @@ <finalName>${project.groupId}.${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.openidentityplatform.maven.plugins</groupId> <artifactId>javadoc-updater-maven-plugin</artifactId> <version>1.0.0</version> <executions> <execution> <phase>site</phase> <goals> <goal>fixjavadoc</goal> </goals> <configuration> <directory>${project.reporting.outputDirectory}</directory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <executions>