001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.replication.plugin; 018 019import static org.opends.server.replication.plugin.HistAttrModificationKey.*; 020 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.Set; 024 025import org.forgerock.opendj.ldap.ByteString; 026import org.forgerock.opendj.ldap.ModificationType; 027import org.forgerock.opendj.ldap.schema.AttributeType; 028import org.opends.server.replication.common.CSN; 029import org.opends.server.types.Attribute; 030import org.opends.server.types.Entry; 031import org.opends.server.types.Modification; 032 033/** 034 * This class is used to store historical information for single valued attributes. 035 * One object of this type is created for each attribute that was changed in the entry. 036 * It allows to record the last time a given value was added, 037 * and the last time the whole attribute was deleted. 038 */ 039public class AttrHistoricalSingle extends AttrHistorical 040{ 041 /** Last added value. */ 042 private ByteString value; 043 /** Attribute type for this historical value */ 044 private AttributeType attributeType; 045 /** Last time when a value was added. */ 046 private CSN addTime; 047 /** Last time when the attribute was deleted. */ 048 private CSN deleteTime; 049 /** 050 * Last operation applied. This is only used for multiple mods on the same 051 * single valued attribute in the same modification. 052 */ 053 private HistAttrModificationKey lastMod; 054 055 /** 056 * Builds an {@link AttrHistoricalSingle} object. 057 * 058 * @param attributeType 059 * the attribute type for this historical value 060 */ 061 public AttrHistoricalSingle(AttributeType attributeType) 062 { 063 this.attributeType = attributeType; 064 } 065 066 @Override 067 public CSN getDeleteTime() 068 { 069 return this.deleteTime; 070 } 071 072 @Override 073 public Set<AttrValueHistorical> getValuesHistorical() 074 { 075 if (addTime != null) 076 { 077 return Collections.singleton(new AttrValueHistorical(value, attributeType, addTime, null)); 078 } 079 return Collections.emptySet(); 080 } 081 082 @Override 083 public void processLocalOrNonConflictModification(CSN csn, Modification mod) 084 { 085 Attribute modAttr = mod.getAttribute(); 086 ByteString newValue = getSingleValue(modAttr); 087 088 switch (mod.getModificationType().asEnum()) 089 { 090 case DELETE: 091 delete(csn, newValue); 092 break; 093 094 case ADD: 095 add(csn, newValue); 096 break; 097 098 case REPLACE: 099 replaceOrDelete(csn, newValue); 100 break; 101 102 case INCREMENT: 103 /* FIXME : we should update CSN */ 104 break; 105 } 106 } 107 108 private void replaceOrDelete(CSN csn, ByteString newValue) 109 { 110 if (newValue != null) 111 { 112 replace(csn, newValue); 113 } 114 else 115 { 116 delete(csn, null); 117 } 118 } 119 120 private void add(CSN csn, ByteString newValue) 121 { 122 addTime = csn; 123 value = newValue; 124 lastMod = ADD; 125 } 126 127 private void replace(CSN csn, ByteString newValue) 128 { 129 addTime = csn; 130 deleteTime = csn; 131 value = newValue; 132 lastMod = REPL; 133 } 134 135 private void delete(CSN csn, ByteString newValue) 136 { 137 addTime = null; 138 deleteTime = csn; 139 value = newValue; 140 lastMod = DEL; 141 } 142 143 private void deleteWithoutDeleteTime() 144 { 145 addTime = null; 146 value = null; 147 lastMod = DEL; 148 } 149 150 @Override 151 public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn, 152 Entry modifiedEntry, Modification mod) 153 { 154 Attribute modAttr = mod.getAttribute(); 155 ByteString newValue = getSingleValue(modAttr); 156 157 boolean conflict = false; 158 switch (mod.getModificationType().asEnum()) 159 { 160 case DELETE: 161 if (csn.isNewerThan(addTime)) 162 { 163 if (newValue == null || newValue.equals(value) || value == null) 164 { 165 if (csn.isNewerThan(deleteTime)) 166 { 167 deleteTime = csn; 168 } 169 AttributeType type = modAttr.getAttributeDescription().getAttributeType(); 170 if (!modifiedEntry.hasAttribute(type)) 171 { 172 conflict = true; 173 modsIterator.remove(); 174 } 175 else if (newValue != null && 176 !modifiedEntry.hasValue(modAttr.getAttributeDescription(), newValue)) 177 { 178 conflict = true; 179 modsIterator.remove(); 180 } 181 else 182 { 183 deleteWithoutDeleteTime(); 184 } 185 } 186 else 187 { 188 conflict = true; 189 modsIterator.remove(); 190 } 191 } 192 else if (csn.equals(addTime)) 193 { 194 if (lastMod == ADD || lastMod == REPL) 195 { 196 if (csn.isNewerThan(deleteTime)) 197 { 198 deleteTime = csn; 199 } 200 deleteWithoutDeleteTime(); 201 } 202 else 203 { 204 conflict = true; 205 modsIterator.remove(); 206 } 207 } 208 else 209 { 210 conflict = true; 211 modsIterator.remove(); 212 } 213 break; 214 215 case ADD: 216 if (csn.isNewerThanOrEqualTo(deleteTime) && csn.isOlderThan(addTime)) 217 { 218 conflict = true; 219 mod.setModificationType(ModificationType.REPLACE); 220 addTime = csn; 221 value = newValue; 222 lastMod = REPL; 223 } 224 else 225 { 226 if (csn.isNewerThanOrEqualTo(deleteTime) 227 && (addTime == null || addTime.isOlderThan(deleteTime))) 228 { 229 add(csn, newValue); 230 } 231 else 232 { 233 // Case where CSN = addTime = deleteTime 234 if (csn.equals(deleteTime) && csn.equals(addTime) 235 && lastMod == DEL) 236 { 237 add(csn, newValue); 238 } 239 else 240 { 241 conflict = true; 242 modsIterator.remove(); 243 } 244 } 245 } 246 247 break; 248 249 case REPLACE: 250 if (csn.isOlderThan(deleteTime)) 251 { 252 conflict = true; 253 modsIterator.remove(); 254 } 255 else 256 { 257 replaceOrDelete(csn, newValue); 258 } 259 break; 260 261 case INCREMENT: 262 /* FIXME : we should update CSN */ 263 break; 264 } 265 return conflict; 266 } 267 268 private ByteString getSingleValue(Attribute modAttr) 269 { 270 if (modAttr != null && !modAttr.isEmpty()) 271 { 272 return modAttr.iterator().next(); 273 } 274 return null; 275 } 276 277 @Override 278 public void assign(HistAttrModificationKey histKey, AttributeType attrType, ByteString value, CSN csn) 279 { 280 switch (histKey) 281 { 282 case ADD: 283 this.addTime = csn; 284 this.value = value; 285 break; 286 287 case DEL: 288 this.deleteTime = csn; 289 if (value != null) 290 { 291 this.value = value; 292 } 293 break; 294 295 case REPL: 296 this.addTime = this.deleteTime = csn; 297 if (value != null) 298 { 299 this.value = value; 300 } 301 break; 302 303 case ATTRDEL: 304 this.deleteTime = csn; 305 break; 306 } 307 } 308 309 @Override 310 public String toString() 311 { 312 final StringBuilder sb = new StringBuilder(); 313 if (deleteTime != null) 314 { 315 sb.append("deleteTime=").append(deleteTime); 316 } 317 if (addTime != null) 318 { 319 if (sb.length() > 0) 320 { 321 sb.append(", "); 322 } 323 sb.append("addTime=").append(addTime); 324 } 325 if (sb.length() > 0) 326 { 327 sb.append(", "); 328 } 329 sb.append("value=").append(value) 330 .append(", lastMod=").append(lastMod); 331 return getClass().getSimpleName() + "(" + sb + ")"; 332 } 333}