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}