/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2014 ForgeRock AS. */ package org.opends.server.backends; import static org.opends.messages.BackendMessages.*; import static org.opends.messages.ReplicationMessages.*; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType.*; import static org.opends.server.replication.server.changelog.api.DBCursor.PositionStrategy.*; import static org.opends.server.util.LDIFWriter.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.text.SimpleDateFormat; import java.util.*; import org.opends.messages.Category; import org.opends.messages.Message; import org.opends.messages.Severity; import org.opends.server.admin.Configuration; import org.opends.server.api.Backend; import org.opends.server.config.ConfigConstants; import org.opends.server.config.ConfigException; import org.opends.server.controls.EntryChangelogNotificationControl; import org.opends.server.controls.ExternalChangelogRequestControl; import org.opends.server.core.*; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.replication.common.CSN; import org.opends.server.replication.common.MultiDomainServerState; import org.opends.server.replication.plugin.MultimasterReplication; import org.opends.server.replication.protocol.AddMsg; import org.opends.server.replication.protocol.DeleteMsg; import org.opends.server.replication.protocol.LDAPUpdateMsg; import org.opends.server.replication.protocol.ModifyCommonMsg; import org.opends.server.replication.protocol.ModifyDNMsg; import org.opends.server.replication.protocol.StartECLSessionMsg.ECLRequestType; import org.opends.server.replication.protocol.UpdateMsg; import org.opends.server.replication.server.ReplicationServer; import org.opends.server.replication.server.changelog.api.ChangeNumberIndexDB; import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord; import org.opends.server.replication.server.changelog.api.ChangelogDB; import org.opends.server.replication.server.changelog.api.ChangelogException; import org.opends.server.replication.server.changelog.api.DBCursor; import org.opends.server.replication.server.changelog.api.ReplicationDomainDB; import org.opends.server.replication.server.changelog.je.ECLEnabledDomainPredicate; import org.opends.server.replication.server.changelog.je.ECLMultiDomainDBCursor; import org.opends.server.replication.server.changelog.je.MultiDomainDBCursor; import org.opends.server.types.*; import org.opends.server.util.StaticUtils; /** * A backend that provides access to the changelog, ie the "cn=changelog" * suffix. It is a read-only backend that is created by a * {@code ReplicationServer} and is not configurable. *
* There are two modes to search the changelog: *
changeNumber attribute is not returned with
* the entries.changeNumber attribute value is set
* from the content of ChangeNumberIndexDB.* Map addMsg to an LDIF string for the 'changes' attribute, and pull out * change initiators name if available which is contained in the creatorsName * attribute. */ private Entry createAddMsg(final DN baseDN, final long changeNumber, final String cookie, final UpdateMsg msg) throws DirectoryException { final AddMsg addMsg = (AddMsg) msg; String changeInitiatorsName = null; String ldifChanges = null; try { final StringBuilder builder = new StringBuilder(256); for (Attribute attr : addMsg.getAttributes()) { if (attr.getAttributeType().equals(CREATORS_NAME_TYPE) && !attr.isEmpty()) { // This attribute is not multi-valued. changeInitiatorsName = attr.iterator().next().toString(); } final String attrName = attr.getNameWithOptions(); for (AttributeValue value : attr) { builder.append(attrName); appendLDIFSeparatorAndValue(builder, value.getValue()); builder.append('\n'); } } ldifChanges = builder.toString(); } catch (Exception e) { logEncodingMessageError("add", addMsg.getDN(), e); } return createChangelogEntry(baseDN, changeNumber, cookie, addMsg, ldifChanges, "add", changeInitiatorsName); } /** * Creates an entry from a modify message. *
* Map the modifyMsg to an LDIF string for the 'changes' attribute, and pull
* out change initiators name if available which is contained in the
* modifiersName attribute.
*/
private Entry createModifyMsg(final DN baseDN, final long changeNumber, final String cookie, final UpdateMsg msg)
throws DirectoryException
{
final ModifyCommonMsg modifyMsg = (ModifyCommonMsg) msg;
String changeInitiatorsName = null;
String ldifChanges = null;
try
{
final StringBuilder builder = new StringBuilder(128);
for (Modification mod : modifyMsg.getMods())
{
final Attribute attr = mod.getAttribute();
if (mod.getModificationType() == ModificationType.REPLACE
&& attr.getAttributeType().equals(MODIFIERS_NAME_TYPE)
&& !attr.isEmpty())
{
// This attribute is not multi-valued.
changeInitiatorsName = attr.iterator().next().toString();
}
final String attrName = attr.getNameWithOptions();
builder.append(mod.getModificationType().getLDIFName());
builder.append(": ");
builder.append(attrName);
builder.append('\n');
for (AttributeValue value : attr)
{
builder.append(attrName);
appendLDIFSeparatorAndValue(builder, value.getValue());
builder.append('\n');
}
builder.append("-\n");
}
ldifChanges = builder.toString();
}
catch (Exception e)
{
logEncodingMessageError("modify", modifyMsg.getDN(), e);
}
final boolean isModifyDNMsg = modifyMsg instanceof ModifyDNMsg;
final Entry entry = createChangelogEntry(baseDN, changeNumber, cookie, modifyMsg, ldifChanges,
isModifyDNMsg ? "modrdn" : "modify", changeInitiatorsName);
if (isModifyDNMsg)
{
final ModifyDNMsg modDNMsg = (ModifyDNMsg) modifyMsg;
addAttribute(entry, "newrdn", modDNMsg.getNewRDN());
if (modDNMsg.getNewSuperior() != null)
{
addAttribute(entry, "newsuperior", modDNMsg.getNewSuperior());
}
addAttribute(entry, "deleteoldrdn", String.valueOf(modDNMsg.deleteOldRdn()));
}
return entry;
}
/**
* Log an encoding message error.
*
* @param messageType
* String identifying type of message. Should be "add" or "modify".
* @param entryDN
* DN of original entry
*/
private void logEncodingMessageError(String messageType, DN entryDN, Exception exception)
{
TRACER.debugCaught(DebugLogLevel.ERROR, exception);
logError(Message.raw(Category.SYNC, Severity.MILD_ERROR,
"An exception was encountered while trying to encode a replication " + messageType + " message for entry \""
+ entryDN + "\" into an External Change Log entry: " + exception.getMessage()));
}
/**
* Create a changelog entry from a set of provided information. This is the part of
* entry creation common to all types of msgs (ADD, DEL, MOD, MODDN).
*/
private static Entry createChangelogEntry(final DN baseDN, final long changeNumber, final String cookie,
final LDAPUpdateMsg msg, final String ldifChanges, final String changeType,
final String changeInitiatorsName) throws DirectoryException
{
final CSN csn = msg.getCSN();
String dnString;
if (changeNumber == 0)
{
// Cookie mode
dnString = "replicationCSN=" + csn + "," + baseDN.toString() + "," + DN_EXTERNAL_CHANGELOG_ROOT;
}
else
{
// Draft compat mode
dnString = "changeNumber=" + changeNumber + "," + DN_EXTERNAL_CHANGELOG_ROOT;
}
final Map