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.opends.server.replication.common.CSN; 028import org.opends.server.types.Attribute; 029import org.forgerock.opendj.ldap.schema.AttributeType; 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 time when the attribute was deleted. */ 042 private CSN deleteTime; 043 /** Last time when a value was added. */ 044 private CSN addTime; 045 /** Last added value. */ 046 private ByteString value; 047 /** 048 * Last operation applied. This is only used for multiple mods on the same 049 * single valued attribute in the same modification. 050 */ 051 private HistAttrModificationKey lastMod; 052 053 @Override 054 public CSN getDeleteTime() 055 { 056 return this.deleteTime; 057 } 058 059 @Override 060 public Set<AttrValueHistorical> getValuesHistorical() 061 { 062 if (addTime != null) 063 { 064 return Collections.singleton(new AttrValueHistorical(value, addTime, null)); 065 } 066 return Collections.emptySet(); 067 } 068 069 @Override 070 public void processLocalOrNonConflictModification(CSN csn, Modification mod) 071 { 072 Attribute modAttr = mod.getAttribute(); 073 ByteString newValue = getSingleValue(modAttr); 074 075 switch (mod.getModificationType().asEnum()) 076 { 077 case DELETE: 078 delete(csn, newValue); 079 break; 080 081 case ADD: 082 add(csn, newValue); 083 break; 084 085 case REPLACE: 086 replaceOrDelete(csn, newValue); 087 break; 088 089 case INCREMENT: 090 /* FIXME : we should update CSN */ 091 break; 092 } 093 } 094 095 private void replaceOrDelete(CSN csn, ByteString newValue) 096 { 097 if (newValue != null) 098 { 099 replace(csn, newValue); 100 } 101 else 102 { 103 delete(csn, null); 104 } 105 } 106 107 private void add(CSN csn, ByteString newValue) 108 { 109 addTime = csn; 110 value = newValue; 111 lastMod = ADD; 112 } 113 114 private void replace(CSN csn, ByteString newValue) 115 { 116 addTime = csn; 117 deleteTime = csn; 118 value = newValue; 119 lastMod = REPL; 120 } 121 122 private void delete(CSN csn, ByteString newValue) 123 { 124 addTime = null; 125 deleteTime = csn; 126 value = newValue; 127 lastMod = DEL; 128 } 129 130 private void deleteWithoutDeleteTime() 131 { 132 addTime = null; 133 value = null; 134 lastMod = DEL; 135 } 136 137 @Override 138 public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn, 139 Entry modifiedEntry, Modification mod) 140 { 141 Attribute modAttr = mod.getAttribute(); 142 ByteString newValue = getSingleValue(modAttr); 143 144 boolean conflict = false; 145 switch (mod.getModificationType().asEnum()) 146 { 147 case DELETE: 148 if (csn.isNewerThan(addTime)) 149 { 150 if (newValue == null || newValue.equals(value) || value == null) 151 { 152 if (csn.isNewerThan(deleteTime)) 153 { 154 deleteTime = csn; 155 } 156 AttributeType type = modAttr.getAttributeDescription().getAttributeType(); 157 if (!modifiedEntry.hasAttribute(type)) 158 { 159 conflict = true; 160 modsIterator.remove(); 161 } 162 else if (newValue != null && 163 !modifiedEntry.hasValue(modAttr.getAttributeDescription(), newValue)) 164 { 165 conflict = true; 166 modsIterator.remove(); 167 } 168 else 169 { 170 deleteWithoutDeleteTime(); 171 } 172 } 173 else 174 { 175 conflict = true; 176 modsIterator.remove(); 177 } 178 } 179 else if (csn.equals(addTime)) 180 { 181 if (lastMod == ADD || lastMod == REPL) 182 { 183 if (csn.isNewerThan(deleteTime)) 184 { 185 deleteTime = csn; 186 } 187 deleteWithoutDeleteTime(); 188 } 189 else 190 { 191 conflict = true; 192 modsIterator.remove(); 193 } 194 } 195 else 196 { 197 conflict = true; 198 modsIterator.remove(); 199 } 200 break; 201 202 case ADD: 203 if (csn.isNewerThanOrEqualTo(deleteTime) && csn.isOlderThan(addTime)) 204 { 205 conflict = true; 206 mod.setModificationType(ModificationType.REPLACE); 207 addTime = csn; 208 value = newValue; 209 lastMod = REPL; 210 } 211 else 212 { 213 if (csn.isNewerThanOrEqualTo(deleteTime) 214 && (addTime == null || addTime.isOlderThan(deleteTime))) 215 { 216 add(csn, newValue); 217 } 218 else 219 { 220 // Case where CSN = addTime = deleteTime 221 if (csn.equals(deleteTime) && csn.equals(addTime) 222 && lastMod == DEL) 223 { 224 add(csn, newValue); 225 } 226 else 227 { 228 conflict = true; 229 modsIterator.remove(); 230 } 231 } 232 } 233 234 break; 235 236 case REPLACE: 237 if (csn.isOlderThan(deleteTime)) 238 { 239 conflict = true; 240 modsIterator.remove(); 241 } 242 else 243 { 244 replaceOrDelete(csn, newValue); 245 } 246 break; 247 248 case INCREMENT: 249 /* FIXME : we should update CSN */ 250 break; 251 } 252 return conflict; 253 } 254 255 private ByteString getSingleValue(Attribute modAttr) 256 { 257 if (modAttr != null && !modAttr.isEmpty()) 258 { 259 return modAttr.iterator().next(); 260 } 261 return null; 262 } 263 264 @Override 265 public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn) 266 { 267 switch (histKey) 268 { 269 case ADD: 270 this.addTime = csn; 271 this.value = value; 272 break; 273 274 case DEL: 275 this.deleteTime = csn; 276 if (value != null) 277 { 278 this.value = value; 279 } 280 break; 281 282 case REPL: 283 this.addTime = this.deleteTime = csn; 284 if (value != null) 285 { 286 this.value = value; 287 } 288 break; 289 290 case ATTRDEL: 291 this.deleteTime = csn; 292 break; 293 } 294 } 295 296 @Override 297 public String toString() 298 { 299 final StringBuilder sb = new StringBuilder(); 300 if (deleteTime != null) 301 { 302 sb.append("deleteTime=").append(deleteTime); 303 } 304 if (addTime != null) 305 { 306 if (sb.length() > 0) 307 { 308 sb.append(", "); 309 } 310 sb.append("addTime=").append(addTime); 311 } 312 if (sb.length() > 0) 313 { 314 sb.append(", "); 315 } 316 sb.append("value=").append(value) 317 .append(", lastMod=").append(lastMod); 318 return getClass().getSimpleName() + "(" + sb + ")"; 319 } 320}