/* * 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 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS. */ package org.opends.server.replication.plugin; import static org.opends.server.replication.plugin.HistAttrModificationKey.*; import static org.opends.server.util.StaticUtils.*; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ModificationType; import org.opends.server.core.DirectoryServer; import org.opends.server.replication.common.CSN; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeBuilder; import org.opends.server.types.AttributeDescription; import org.opends.server.types.AttributeType; import org.opends.server.types.Modification; /** * This class stores an internal usable representation of the value of * the historical related to an entry. * It encodes/decodes from the String form stored in the DB attribute * from/to the internal usable form. * * * an historical attribute value looks like : * description:00000108b3a65541000000000001:add:added_value * or * description:00000108b3a6cbb8000000000001:del:deleted_value * or * description:00000108b3a6cbb8000000000001:repl:new_value * or * description:00000108b3a6cbb8000000000001:attrDel * or * description:00000108b3a65541000000000001:add * or * dn:00000108b3a65541000000000001:add (ADD operation) * or * dn:00000108b3a65541000000000001:moddn (MODIFYDN operation) * * so after split * token[0] will contain the attribute name * token[1] will contain the CSN * token[2] will contain the type of historical information * token[3] will contain the attribute value * * options are stored with the attribute names using; as a separator * example : * description;FR;France:00000108b3a65541000000000001:add:added_value */ class HistoricalAttributeValue { private final AttributeDescription attrDesc; /** The lowercase attribute string representation. */ private final String attrString; private final ByteString attributeValue; private final CSN csn; private final HistAttrModificationKey histKey; private final String stringValue; /** * This flag indicates that this value was generated to store the last date * when the entry was renamed. */ private boolean isModDN; /** * Create a new object from the String encoded form. * * @param strVal The String encoded form of historical attribute value. * @see EntryHistorical#encodeAndPurge() encoding in EntryHistorical */ HistoricalAttributeValue(String strVal) { String[] token = strVal.split(":", 4); Set<String> options; if (token[0].contains(";")) { options = new LinkedHashSet<>(); String[] optionsToken = token[0].split(";"); int index = 1; while (index < optionsToken.length) { options.add(optionsToken[index]); index ++; } attrString = toLowerCase(optionsToken[0]); } else { options = Collections.emptySet(); attrString = toLowerCase(token[0]); } AttributeType attrType; if (attrString.compareTo("dn") != 0) { // This HistVal was used to store the date when some // modifications were done to the entries. attrType = DirectoryServer.getAttributeTypeOrDefault(attrString); } else { // This HistVal is used to store the date when the entry // was added to the directory or when it was last renamed. attrType = null; if (token.length >= 3 && token[2].compareTo("moddn") == 0) { isModDN = true; } } this.attrDesc = attrType != null ? AttributeDescription.create(attrType, options) : null; csn = new CSN(token[1]); histKey = HistAttrModificationKey.decodeKey(token[2]); if (histKey != ATTRDEL) { if (token.length == 4) { stringValue = token[3]; attributeValue = ByteString.valueOfUtf8(stringValue); } else { stringValue = null; attributeValue = null; } } else { stringValue = null; attributeValue = null; } } private AttributeType getAttributeType() { return attrDesc != null ? attrDesc.getAttributeType() : null; } private Set<String> getOptions() { return attrDesc != null ? attrDesc.getOptions() : Collections.<String> emptySet(); } /** * Get the String form of the attribute type. * * @return Returns the String form of the attribute type. */ public String getAttrString() { return attrString; } /** * Get the attribute description of this HistVal. * * @return Returns the attribute description of this HistVal. * Can return {@code null} if the HistVal was generated for a ADD Operation. */ AttributeDescription getAttributeDescription() { return attrDesc; } /** * Get the CSN of this HistVal. * @return Returns the CSN of this HistVal. */ public CSN getCSN() { return csn; } /** * Get the HistKey. * @return Returns the histKey. */ public HistAttrModificationKey getHistKey() { return histKey; } /** * Get the Attribute Value. * @return The Attribute Value. */ public ByteString getAttributeValue() { return attributeValue; } /** * Generate a Modification equivalent to this value of the historical * attribute. * * @return A modification equivalent to this value of the historical * attribute. */ public Modification generateMod() { AttributeBuilder builder = new AttributeBuilder(getAttributeType(), attrString); builder.setOptions(getOptions()); if (histKey != ATTRDEL) { builder.add(attributeValue); } Attribute attr = builder.toAttribute(); switch (histKey) { case ADD: return new Modification(ModificationType.ADD, attr); case DEL: case ATTRDEL: return new Modification(ModificationType.DELETE, attr); case REPL: return new Modification(ModificationType.REPLACE, attr); default: return null; } } /** * Indicates if this value of the historical attribute was generated * for a ADD operation. * * @return a boolean indicating if this was generated for a ADD operation. */ public boolean isADDOperation() { return getAttributeType() == null && !isModDN; } /** * Indicates if this value of the historical attribute was generated * for a MODDN operation. * * @return a boolean indicating if this was generated for a ADDMODDN operation. */ public boolean isMODDNOperation() { return getAttributeType() == null && isModDN; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(attrString); for (String option : getOptions()) { sb.append(";").append(option); } sb.append(":").append(csn).append(":").append(getModificationType()); if (stringValue != null) { sb.append(":").append(stringValue); } return sb.toString(); } private String getModificationType() { if (isModDN) { return "moddn"; } else if (histKey != null) { return histKey.toString(); } return "TODO"; } }