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}