Code cleanup.
ExternalChangeLogTest.java:
Fixed (?) random failures by comparing LDIF values (rightly line ordering insensitive) rather than String values (wrongly line ordering sensitive).
Added checkLDIF() and toLDIFEntries().
In checkValues(), changed assert.
LDAPReplicationDomain.java:
Changed shutdown field from boolean to AtomicBoolean.
Removed shutdown field and initiateShutdown() from RSUpdater.
In buildAndPublishMissingChanges(), removed AtomicBoolean parameter.
HistoricalCsnOrderingTest.java:
Consequence of the change to LDAPReplicationDomain.buildAndPublishMissingChanges().
Reduced visibilities + added final keyword to fields.
DomainFakeCfg.java:
Removed 2 unused ctors, addECLDomainAddListener(), removeECLDomainAddListener(), addECLDomainDeleteListener(), removeECLDomainDeleteListener().
CryptoManagerImpl.java:
Fixed javadocs.
Removed useless StringBuilder uses.
| | |
| | | */ |
| | | package org.opends.server.crypto; |
| | | |
| | | import java.io.*; |
| | | import java.io.InputStream; |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.PrintStream; |
| | | import java.security.*; |
| | | import java.security.cert.Certificate; |
| | | import java.security.cert.CertificateFactory; |
| | |
| | | */ |
| | | private static final SecureRandom secureRandom = new SecureRandom(); |
| | | |
| | | /** |
| | | * The random number generator used for initialization vector production. |
| | | */ |
| | | /** The random number generator used for initialization vector production. */ |
| | | private static final Random pseudoRandom |
| | | = new Random(secureRandom.nextLong()); |
| | | |
| | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ConfigChangeResult applyConfigurationChange( |
| | | CryptoManagerCfg cfg) |
| | | public ConfigChangeResult applyConfigurationChange(CryptoManagerCfg cfg) |
| | | { |
| | | ResultCode resultCode = ResultCode.SUCCESS; |
| | | boolean adminActionRequired = false; |
| | | List<LocalizableMessage> messages = new ArrayList<LocalizableMessage>(); |
| | | |
| | | preferredDigestAlgorithm = cfg.getDigestAlgorithm(); |
| | | preferredMACAlgorithm = cfg.getMacAlgorithm(); |
| | | preferredMACAlgorithmKeyLengthBits = cfg.getMacKeyLength(); |
| | | preferredCipherTransformation = cfg.getCipherTransformation(); |
| | | preferredCipherTransformationKeyLengthBits = cfg.getCipherKeyLength(); |
| | | preferredKeyWrappingTransformation = cfg.getKeyWrappingTransformation(); |
| | | return new ConfigChangeResult(resultCode, adminActionRequired, messages); |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false, |
| | | new ArrayList<LocalizableMessage>()); |
| | | } |
| | | |
| | | |
| | |
| | | serversDN, SearchScope.SUBORDINATES, |
| | | SearchFilter.createFilterFromString(filter)); |
| | | if (internalSearch.getResultCode() != ResultCode.SUCCESS) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | LinkedList<SearchResultEntry> resultEntries = |
| | | internalSearch.getSearchEntries(); |
| | |
| | | void importCipherKeyEntry(Entry entry) |
| | | throws CryptoManagerException |
| | | { |
| | | // Ignore the entry if it does not have the appropriate |
| | | // objectclass. |
| | | if (!entry.hasObjectClass(ocCipherKey)) return; |
| | | // Ignore the entry if it does not have the appropriate objectclass. |
| | | if (!entry.hasObjectClass(ocCipherKey)) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | try |
| | | { |
| | |
| | | for (String symmetricKey : symmetricKeys) |
| | | { |
| | | secretKey = decodeSymmetricKeyAttribute(symmetricKey); |
| | | if (secretKey != null) break; |
| | | if (secretKey != null) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (null != secretKey) { |
| | |
| | | void importMacKeyEntry(Entry entry) |
| | | throws CryptoManagerException |
| | | { |
| | | // Ignore the entry if it does not have the appropriate |
| | | // objectclass. |
| | | if (!entry.hasObjectClass(ocMacKey)) return; |
| | | // Ignore the entry if it does not have the appropriate objectclass. |
| | | if (!entry.hasObjectClass(ocMacKey)) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | try |
| | | { |
| | |
| | | for (String symmetricKey : symmetricKeys) |
| | | { |
| | | secretKey = decodeSymmetricKeyAttribute(symmetricKey); |
| | | if (secretKey != null) break; |
| | | if (secretKey != null) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (secretKey == null) |
| | |
| | | final String transformation, |
| | | final int keyLengthBits) |
| | | throws CryptoManagerException { |
| | | |
| | | final Map<KeyEntryID, CipherKeyEntry> cache |
| | | = (null == cryptoManager) |
| | | ? null : cryptoManager.cipherKeyEntryCache; |
| | | final Map<KeyEntryID, CipherKeyEntry> cache = |
| | | cryptoManager != null ? cryptoManager.cipherKeyEntryCache : null; |
| | | |
| | | CipherKeyEntry keyEntry = new CipherKeyEntry(transformation, |
| | | keyLengthBits); |
| | | |
| | | // Validate the key entry. Record the initialization vector length, if |
| | | // any. |
| | | // Validate the key entry. Record the initialization vector length, if any |
| | | final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null); |
| | | // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2471 |
| | | final byte[] iv = cipher.getIV(); |
| | | keyEntry.setIVLengthBits((null == iv) ? 0 : iv.length * Byte.SIZE); |
| | | keyEntry.setIVLengthBits(null == iv ? 0 : iv.length * Byte.SIZE); |
| | | |
| | | if (null != cache) { |
| | | /* The key is published to ADS before making it available in the local |
| | |
| | | private static String keyAlgorithmFromTransformation( |
| | | String transformation){ |
| | | final int separatorIndex = transformation.indexOf('/'); |
| | | return (0 < separatorIndex) |
| | | return 0 < separatorIndex |
| | | ? transformation.substring(0, separatorIndex) |
| | | : transformation; |
| | | } |
| | |
| | | throws CryptoManagerException { |
| | | Reject.ifNull(algorithm); |
| | | |
| | | final Map<KeyEntryID, MacKeyEntry> cache = (null == cryptoManager) |
| | | ? null : cryptoManager.macKeyEntryCache; |
| | | final Map<KeyEntryID, MacKeyEntry> cache = |
| | | cryptoManager != null ? cryptoManager.macKeyEntryCache : null; |
| | | |
| | | final MacKeyEntry keyEntry = new MacKeyEntry(algorithm, keyLengthBits); |
| | | |
| | |
| | | { |
| | | final MacKeyEntry keyEntry = MacKeyEntry.getKeyEntry(this, |
| | | new KeyEntryID(keyEntryID)); |
| | | return (null == keyEntry) ? null : getMacEngine(keyEntry); |
| | | return keyEntry != null ? getMacEngine(keyEntry) : null; |
| | | } |
| | | |
| | | |
| | |
| | | final byte[] keyID = keyEntry.getKeyID().getByteValue(); |
| | | final byte[] iv = cipher.getIV(); |
| | | final int prologueLength |
| | | = /* version */ 1 + keyID.length + ((null == iv) ? 0 : iv.length); |
| | | = /* version */ 1 + keyID.length + (iv != null ? iv.length : 0); |
| | | final int dataLength = cipher.getOutputSize(data.length); |
| | | final byte[] cipherText = new byte[prologueLength + dataLength]; |
| | | int writeIndex = 0; |
| | |
| | | |
| | | final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE, iv); |
| | | if(data.length - readIndex > 0) |
| | | { |
| | | return cipher.doFinal(data, readIndex, data.length - readIndex); |
| | | else { |
| | | //IBM Java 6 throws an IllegalArgumentException when there's n |
| | | } |
| | | else |
| | | { |
| | | // IBM Java 6 throws an IllegalArgumentException when there's no |
| | | // data to process. |
| | | return cipher.doFinal(); |
| | | } |
| | |
| | | private boolean solveConflictFlag = true; |
| | | |
| | | private final InternalClientConnection conn = getRootConnection(); |
| | | private volatile boolean shutdown = false; |
| | | private final AtomicBoolean shutdown = new AtomicBoolean(); |
| | | private volatile boolean disabled = false; |
| | | private volatile boolean stateSavingDisabled = false; |
| | | |
| | |
| | | */ |
| | | |
| | | /** Holds the fractional configuration for this domain, if any. */ |
| | | private FractionalConfig fractionalConfig; |
| | | private final FractionalConfig fractionalConfig; |
| | | |
| | | /** |
| | | * The list of attributes that cannot be used in fractional replication |
| | |
| | | private class RSUpdater extends DirectoryThread |
| | | { |
| | | private final CSN startCSN; |
| | | /** |
| | | * Used to communicate that the current thread computation needs to |
| | | * shutdown. |
| | | */ |
| | | private AtomicBoolean shutdown = new AtomicBoolean(false); |
| | | |
| | | protected RSUpdater(CSN replServerMaxCSN) |
| | | { |
| | |
| | | @Override |
| | | public void run() |
| | | { |
| | | // Replication server is missing some of our changes: let's |
| | | // send them to him. |
| | | // Replication server is missing some of our changes: |
| | | // let's send them to him. |
| | | logger.trace(DEBUG_GOING_TO_SEARCH_FOR_CHANGES); |
| | | |
| | | /* |
| | |
| | | */ |
| | | try |
| | | { |
| | | if (buildAndPublishMissingChanges(startCSN, broker, shutdown)) |
| | | if (buildAndPublishMissingChanges(startCSN, broker)) |
| | | { |
| | | logger.trace(DEBUG_CHANGES_SENT); |
| | | synchronized(replayOperations) |
| | |
| | | */ |
| | | logger.error(ERR_CANNOT_RECOVER_CHANGES, getBaseDNString()); |
| | | } |
| | | } catch (Exception e) |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | /* |
| | | * An error happened trying to search for the updates |
| | |
| | | rsUpdater.compareAndSet(this, null); |
| | | } |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public void initiateShutdown() |
| | | { |
| | | this.shutdown.set(true); |
| | | super.initiateShutdown(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Creates a new ReplicationDomain using configuration from configEntry. |
| | |
| | | */ |
| | | static class AttributeValueStringIterator implements Iterator<String> |
| | | { |
| | | private Iterator<ByteString> attrValIt; |
| | | private final Iterator<ByteString> attrValIt; |
| | | |
| | | /** |
| | | * Creates a new AttributeValueStringIterator object. |
| | |
| | | */ |
| | | public void shutdown() |
| | | { |
| | | if (!shutdown) |
| | | if (shutdown.compareAndSet(false, true)) |
| | | { |
| | | shutdown = true; |
| | | final RSUpdater rsUpdater = this.rsUpdater.get(); |
| | | if (rsUpdater != null) |
| | | { |
| | |
| | | disableService(); |
| | | } |
| | | |
| | | // wait for completion of the persistentServerState thread. |
| | | // wait for completion of the ServerStateFlush thread. |
| | | try |
| | | { |
| | | while (!done) |
| | |
| | | * The CSN where we need to start the search |
| | | * @param session |
| | | * The session to use to publish the changes |
| | | * @param shutdown |
| | | * whether the current run must be stopped |
| | | * @return A boolean indicating he success of the operation. |
| | | * @throws Exception |
| | | * if an Exception happens during the search. |
| | | */ |
| | | boolean buildAndPublishMissingChanges(CSN startCSN, |
| | | ReplicationBroker session, AtomicBoolean shutdown) throws Exception |
| | | boolean buildAndPublishMissingChanges(CSN startCSN, ReplicationBroker session) |
| | | throws Exception |
| | | { |
| | | // Trim the changes in replayOperations that are older than the startCSN. |
| | | synchronized (replayOperations) |
| | |
| | | /** |
| | | * Base DN the fractional configuration is for. |
| | | */ |
| | | private DN baseDN; |
| | | private final DN baseDN; |
| | | |
| | | /** |
| | | * Constructs a new fractional configuration object. |
| | |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import java.util.List; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | |
| | | import org.opends.server.admin.server.ConfigurationAddListener; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.opends.server.admin.server.ConfigurationChangeListener; |
| | | import org.opends.server.admin.server.ConfigurationDeleteListener; |
| | | import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.AssuredType; |
| | | import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.IsolationPolicy; |
| | | import org.opends.server.admin.std.server.ExternalChangelogDomainCfg; |
| | | import org.opends.server.admin.std.server.ReplicationDomainCfg; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | |
| | |
| | | */ |
| | | public class DomainFakeCfg implements ReplicationDomainCfg |
| | | { |
| | | private DN baseDN; |
| | | private int serverId; |
| | | private SortedSet<String> replicationServers; |
| | | private final DN baseDN; |
| | | private final int serverId; |
| | | private final SortedSet<String> replicationServers; |
| | | private long heartbeatInterval = 1000; |
| | | |
| | | /** |
| | |
| | | /** Timeout (in milliseconds) when waiting for acknowledgments */ |
| | | private long assuredTimeout = 1000; |
| | | /** Group id */ |
| | | private int groupId = 1; |
| | | private final int groupId; |
| | | /** Referrals urls to be published to other servers of the topology */ |
| | | private SortedSet<String> refUrls = new TreeSet<String>(); |
| | | |
| | | private SortedSet<String> fractionalExcludes = new TreeSet<String>(); |
| | | private SortedSet<String> fractionalIncludes = new TreeSet<String>(); |
| | | private final SortedSet<String> fractionalExcludes = new TreeSet<String>(); |
| | | private final SortedSet<String> fractionalIncludes = new TreeSet<String>(); |
| | | |
| | | private ExternalChangelogDomainCfg eclCfg = |
| | | new ExternalChangelogDomainFakeCfg(true, null, null); |
| | |
| | | */ |
| | | public DomainFakeCfg(DN baseDN, int serverId, SortedSet<String> replServers) |
| | | { |
| | | this.baseDN = baseDN; |
| | | this.serverId = serverId; |
| | | this.replicationServers = replServers; |
| | | } |
| | | |
| | | /** |
| | | * Creates a new Domain with the provided information |
| | | * (with some fractional configuration provided) |
| | | */ |
| | | public DomainFakeCfg(DN baseDN, int serverId, SortedSet<String> replServers, |
| | | List<String> fractionalExcludes, List<String> fractionalIncludes) |
| | | { |
| | | this(baseDN, serverId, replServers); |
| | | if (fractionalExcludes != null) |
| | | { |
| | | for (String str : fractionalExcludes) |
| | | { |
| | | this.fractionalExcludes.add(str); |
| | | } |
| | | } |
| | | if (fractionalIncludes != null) |
| | | { |
| | | for (String str : fractionalIncludes) |
| | | { |
| | | this.fractionalIncludes.add(str); |
| | | } |
| | | } |
| | | this(baseDN, serverId, replServers, -1); |
| | | } |
| | | |
| | | /** |
| | |
| | | public DomainFakeCfg(DN baseDN, int serverId, SortedSet<String> replServers, |
| | | int groupId) |
| | | { |
| | | this(baseDN, serverId, replServers); |
| | | this.baseDN = baseDN; |
| | | this.serverId = serverId; |
| | | this.replicationServers = replServers; |
| | | this.groupId = groupId; |
| | | } |
| | | |
| | |
| | | AssuredType assuredType, int assuredSdLevel, int groupId, |
| | | long assuredTimeout, SortedSet<String> refUrls) |
| | | { |
| | | this(baseDN, serverId, replServers); |
| | | this(baseDN, serverId, replServers, groupId); |
| | | switch(assuredType) |
| | | { |
| | | case NOT_ASSURED: |
| | |
| | | break; |
| | | } |
| | | this.assuredSdLevel = assuredSdLevel; |
| | | this.groupId = groupId; |
| | | this.assuredTimeout = assuredTimeout; |
| | | if (refUrls != null) |
| | | this.refUrls = refUrls; |
| | | } |
| | | |
| | | /** |
| | | * Create a new Domain from the provided arguments. |
| | | * |
| | | * @param baseDN The baseDN in string form. |
| | | * @param serverId The serverID. |
| | | * @param replServer The replication Server that will be used. |
| | | * |
| | | * @throws DirectoryException When the provided string is not a valid DN. |
| | | */ |
| | | public DomainFakeCfg(String baseDN, int serverId, String replServer) |
| | | throws DirectoryException |
| | | { |
| | | this.replicationServers = new TreeSet<String>(); |
| | | this.replicationServers.add(replServer); |
| | | this.baseDN = DN.valueOf(baseDN); |
| | | this.serverId = serverId; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | |
| | | throws ConfigException |
| | | { this.eclCfg=eclCfg;} |
| | | |
| | | |
| | | |
| | | /** |
| | | * Registers to be notified when the ECL Domain is added. |
| | | * |
| | | * @param listener |
| | | * The ECL Domain configuration add listener. |
| | | * @throws ConfigException |
| | | * If the add listener could not be registered. |
| | | */ |
| | | public void addECLDomainAddListener( |
| | | ConfigurationAddListener<ExternalChangelogDomainCfg> listener) |
| | | throws ConfigException |
| | | {} |
| | | |
| | | /** |
| | | * Deregisters an existing ECL Domain configuration add listener. |
| | | * |
| | | * @param listener |
| | | * The ECL Domain configuration add listener. |
| | | */ |
| | | public void removeECLDomainAddListener( |
| | | ConfigurationAddListener<ExternalChangelogDomainCfg> listener) |
| | | {} |
| | | |
| | | /** |
| | | * Registers to be notified the ECL Domain is deleted. |
| | | * |
| | | * @param listener |
| | | * The ECL Domain configuration delete listener. |
| | | * @throws ConfigException |
| | | * If the delete listener could not be registered. |
| | | */ |
| | | public void addECLDomainDeleteListener( |
| | | ConfigurationDeleteListener<ExternalChangelogDomainCfg> listener) |
| | | throws ConfigException |
| | | {} |
| | | |
| | | /** |
| | | * Deregisters an existing ECL Domain configuration delete listener. |
| | | * |
| | | * @param listener |
| | | * The ECL Domain configuration delete listener. |
| | | */ |
| | | public void removeECLDomainDeleteListener( |
| | | ConfigurationDeleteListener<ExternalChangelogDomainCfg> listener) |
| | | {} |
| | | |
| | | @Override |
| | | public boolean isLogChangenumber() |
| | | { |
| | |
| | | import java.util.List; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import org.assertj.core.api.Assertions; |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | |
| | | |
| | | |
| | | private final int serverId = 123; |
| | | private SortedSet<String> replServers = new TreeSet<String>(); |
| | | private final SortedSet<String> replServers = new TreeSet<String>(); |
| | | |
| | | public static class TestBroker extends ReplicationBroker |
| | | private static class TestBroker extends ReplicationBroker |
| | | { |
| | | private List<ReplicationMsg> list; |
| | | private final List<ReplicationMsg> list; |
| | | |
| | | public TestBroker(List<ReplicationMsg> list) |
| | | private TestBroker(List<ReplicationMsg> list) |
| | | { |
| | | super(new DummyReplicationDomain(0), null, |
| | | new DomainFakeCfg(null, 0, TestCaseUtils.<String> newSortedSet()), null); |
| | |
| | | TestBroker session = new TestBroker(opList); |
| | | |
| | | CSN csn = new CSN(startTime, 0, serverId); |
| | | boolean result = rd1.buildAndPublishMissingChanges(csn, session, new AtomicBoolean()); |
| | | boolean result = rd1.buildAndPublishMissingChanges(csn, session); |
| | | assertTrue(result, "buildAndPublishMissingChanges has failed"); |
| | | assertEquals(opList.size(), 3, "buildAndPublishMissingChanges should return 3 operations"); |
| | | assertTrue(opList.getFirst().getClass().equals(AddMsg.class)); |
| | |
| | | opList = new LinkedList<ReplicationMsg>(); |
| | | session = new TestBroker(opList); |
| | | |
| | | result = rd1.buildAndPublishMissingChanges(fromCSN, session, new AtomicBoolean()); |
| | | result = rd1.buildAndPublishMissingChanges(fromCSN, session); |
| | | assertTrue(result, "buildAndPublishMissingChanges has failed"); |
| | | assertEquals(opList.size(), 1, "buildAndPublishMissingChanges should return 1 operation"); |
| | | assertTrue(opList.getFirst().getClass().equals(ModifyMsg.class)); |
| | |
| | | // Call the buildAndPublishMissingChanges and check that this method |
| | | // correctly generates the 4 operations in the correct order. |
| | | CSN csn = new CSN(startTime, 0, serverId); |
| | | boolean result = rd1.buildAndPublishMissingChanges(csn, session, new AtomicBoolean()); |
| | | boolean result = rd1.buildAndPublishMissingChanges(csn, session); |
| | | assertTrue(result, "buildAndPublishMissingChanges has failed"); |
| | | assertEquals(opList.size(), 5, "buildAndPublishMissingChanges should return 5 operations"); |
| | | ReplicationMsg msg = opList.removeFirst(); |
| | |
| | | /** The LDAPStatistics object associated with the LDAP connection handler. */ |
| | | private LDAPStatistics ldapStatistics; |
| | | |
| | | private int brokerSessionTimeout = 5000; |
| | | private int maxWindow = 100; |
| | | private final int brokerSessionTimeout = 5000; |
| | | private final int maxWindow = 100; |
| | | |
| | | /** |
| | | * When used in a search operation, it includes all attributes (user and |
| | |
| | | } else if (i==2) |
| | | { |
| | | checkValue(resultEntry, "changetype", "add"); |
| | | String expectedValue1 = "objectClass: domain\nobjectClass: top\n" |
| | | + "entryUUID: 11111111-1111-1111-1111-111111111111\n"; |
| | | checkValue(resultEntry, "changes", expectedValue1); |
| | | checkLDIF(resultEntry, "changes", |
| | | "objectClass: domain", |
| | | "objectClass: top", |
| | | "entryUUID: 11111111-1111-1111-1111-111111111111"); |
| | | checkValue(resultEntry,"targetentryuuid",user1entryUUID); |
| | | } else if (i==3) |
| | | { |
| | | // check the MOD entry has the right content |
| | | checkValue(resultEntry, "changetype", "modify"); |
| | | String expectedValue = |
| | | "replace: description\n" + "description: new value\n-\n"; |
| | | checkValue(resultEntry,"changes",expectedValue); |
| | | checkLDIF(resultEntry, "changes", |
| | | "replace: description", |
| | | "description: new value", |
| | | "-"); |
| | | checkValue(resultEntry,"targetentryuuid",tn+"uuid3"); |
| | | } else if (i==4) |
| | | { |
| | |
| | | |
| | | private static void checkValue(Entry entry, String attrName, String expectedValue) |
| | | { |
| | | assertThat(getAttributeValue(entry, attrName)) |
| | | assertFalse(expectedValue.contains("\n"), |
| | | "Use checkLDIF() method for asserting on value: \"" + expectedValue + "\""); |
| | | final String actualValue = getAttributeValue(entry, attrName); |
| | | assertThat(actualValue) |
| | | .as("In entry " + entry + " incorrect value for attr '" + attrName + "'") |
| | | .isEqualToIgnoringCase(expectedValue); |
| | | } |
| | | |
| | | /** |
| | | * Asserts the attribute value as LDIF to ignore lines ordering. |
| | | */ |
| | | private static void checkLDIF(Entry entry, String attrName, String... expectedLDIFLines) |
| | | { |
| | | final String actualVal = getAttributeValue(entry, attrName); |
| | | final Set<Set<String>> actual = toLDIFEntries(actualVal.split("\n")); |
| | | final Set<Set<String>> expected = toLDIFEntries(expectedLDIFLines); |
| | | assertThat(actual) |
| | | .as("In entry " + entry + " incorrect value for attr '" + attrName + "'") |
| | | .isEqualTo(expected); |
| | | } |
| | | |
| | | /** |
| | | * Returns a data structure allowing to compare arbitrary LDIF lines. The |
| | | * algorithm splits LDIF entries on lines containing only a dash ("-"). It |
| | | * then returns LDIF entries and lines in an LDIF entry in ordering |
| | | * insensitive data structures. |
| | | * <p> |
| | | * Note: a last line with only a dash ("-") is significant. i.e.: |
| | | * |
| | | * <pre> |
| | | * <code> |
| | | * boolean b = toLDIFEntries("-").equals(toLDIFEntries())); |
| | | * System.out.println(b); // prints "false" |
| | | * </code> |
| | | * </pre> |
| | | */ |
| | | private static Set<Set<String>> toLDIFEntries(String... ldifLines) |
| | | { |
| | | final Set<Set<String>> results = new HashSet<Set<String>>(); |
| | | Set<String> ldifEntryLines = new HashSet<String>(); |
| | | for (String ldifLine : ldifLines) |
| | | { |
| | | if (!"-".equals(ldifLine)) |
| | | { |
| | | // same entry keep adding |
| | | ldifEntryLines.add(ldifLine); |
| | | } |
| | | else |
| | | { |
| | | // this is a new entry |
| | | results.add(ldifEntryLines); |
| | | ldifEntryLines = new HashSet<String>(); |
| | | } |
| | | } |
| | | results.add(ldifEntryLines); |
| | | return results; |
| | | } |
| | | |
| | | private static String getAttributeValue(Entry entry, String attrName) |
| | | { |
| | | List<Attribute> attrs = entry.getAttribute(attrName.toLowerCase()); |
| | |
| | | private static void checkValues(Entry entry, String attrName, |
| | | Set<String> expectedValues) |
| | | { |
| | | final Set<String> values = new HashSet<String>(); |
| | | for (Attribute a : entry.getAttribute(attrName)) |
| | | { |
| | | for (ByteString av : a) |
| | | { |
| | | assertThat(expectedValues) |
| | | .as("In entry " + entry + " incorrect value for attr '" + attrName + "'") |
| | | .contains(av.toString()); |
| | | values.add(av.toString()); |
| | | } |
| | | } |
| | | assertThat(values) |
| | | .as("In entry " + entry + " incorrect values for attr '" + attrName + "'") |
| | | .isEqualTo(expectedValues); |
| | | } |
| | | |
| | | /** |
| | |
| | | final SearchResultEntry addEntry = entries.get(++i); |
| | | checkValue(addEntry, "changetype", "add"); |
| | | commonAssert(addEntry, user1entryUUID, firstChangeNumber, i, tn, csns[i]); |
| | | String expectedValue1 = "objectClass: domain\nobjectClass: top\n" + "entryUUID: " |
| | | + user1entryUUID + "\n"; |
| | | checkValue(addEntry, "changes", expectedValue1); |
| | | checkLDIF(addEntry, "changes", |
| | | "objectClass: domain", |
| | | "objectClass: top", |
| | | "entryUUID: " + user1entryUUID); |
| | | |
| | | // check the MOD entry has the right content |
| | | final SearchResultEntry modEntry = entries.get(++i); |
| | | checkValue(modEntry, "changetype", "modify"); |
| | | commonAssert(modEntry, user1entryUUID, firstChangeNumber, i, tn, csns[i]); |
| | | final String expectedValue = "replace: description\n" + "description: new value\n-\n"; |
| | | checkValue(modEntry, "changes", expectedValue); |
| | | checkLDIF(modEntry, "changes", |
| | | "replace: description", |
| | | "description: new value", |
| | | "-"); |
| | | |
| | | // check the MODDN entry has the right content |
| | | final SearchResultEntry moddnEntry = entries.get(++i); |