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