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 *      Portions Copyright 2012-2015 ForgeRock AS.
025 */
026package org.opends.server.extensions;
027
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.Comparator;
031import java.util.List;
032import java.util.zip.Adler32;
033import java.util.zip.CRC32;
034import java.util.zip.Checksum;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.opendj.config.server.ConfigChangeResult;
038import org.forgerock.opendj.config.server.ConfigException;
039import org.forgerock.opendj.ldap.ByteString;
040import org.forgerock.opendj.ldap.ConditionResult;
041import org.forgerock.opendj.ldap.ResultCode;
042import org.opends.server.admin.server.ConfigurationChangeListener;
043import org.opends.server.admin.std.server.EntityTagVirtualAttributeCfg;
044import org.opends.server.api.VirtualAttributeProvider;
045import org.opends.server.core.SearchOperation;
046import org.opends.server.types.*;
047import org.opends.server.util.StaticUtils;
048
049import static org.opends.messages.ExtensionMessages.*;
050
051/**
052 * This class implements a virtual attribute provider which ensures that all
053 * entries contain an "entity tag" or "Etag" as defined in section 3.11 of RFC
054 * 2616.
055 * <p>
056 * The entity tag may be used by clients, in conjunction with the assertion
057 * control, for optimistic concurrency control, as a way to help prevent
058 * simultaneous updates of an entry from conflicting with each other.
059 */
060public final class EntityTagVirtualAttributeProvider extends
061    VirtualAttributeProvider<EntityTagVirtualAttributeCfg> implements
062    ConfigurationChangeListener<EntityTagVirtualAttributeCfg>
063{
064  private static final Comparator<Attribute> ATTRIBUTE_COMPARATOR =
065      new Comparator<Attribute>()
066  {
067    /** {@inheritDoc} */
068    @Override
069    public int compare(final Attribute a1, final Attribute a2)
070    {
071      return a1.getNameWithOptions().compareTo(a2.getNameWithOptions());
072    }
073  };
074
075  /** Current configuration. */
076  private volatile EntityTagVirtualAttributeCfg config;
077
078
079
080  /**
081   * Default constructor invoked by reflection.
082   */
083  public EntityTagVirtualAttributeProvider()
084  {
085    // Initialization performed by initializeVirtualAttributeProvider.
086  }
087
088
089
090  /** {@inheritDoc} */
091  @Override
092  public ConfigChangeResult applyConfigurationChange(
093      final EntityTagVirtualAttributeCfg configuration)
094  {
095    this.config = configuration;
096    return new ConfigChangeResult();
097  }
098
099
100
101  /** {@inheritDoc} */
102  @Override
103  public ConditionResult approximatelyEqualTo(final Entry entry,
104      final VirtualAttributeRule rule, final ByteString value)
105  {
106    // ETags cannot be used in approximate matching.
107    return ConditionResult.UNDEFINED;
108  }
109
110
111
112  /** {@inheritDoc} */
113  @Override
114  public void finalizeVirtualAttributeProvider()
115  {
116    config.removeEntityTagChangeListener(this);
117  }
118
119
120
121  /** {@inheritDoc} */
122  @Override
123  public Attribute getValues(final Entry entry, final VirtualAttributeRule rule)
124  {
125    // Save reference to current configuration in case it changes.
126    final EntityTagVirtualAttributeCfg cfg = config;
127
128    // Determine which checksum algorithm to use.
129    final Checksum checksummer;
130    switch (cfg.getChecksumAlgorithm())
131    {
132    case CRC_32:
133      checksummer = new CRC32();
134      break;
135    default: // ADLER_32
136      checksummer = new Adler32();
137      break;
138    }
139
140    final ByteString etag = checksumEntry(cfg, checksummer, entry);
141    return Attributes.create(rule.getAttributeType(), etag);
142  }
143
144
145
146  /** {@inheritDoc} */
147  @Override
148  public ConditionResult greaterThanOrEqualTo(final Entry entry,
149      final VirtualAttributeRule rule, final ByteString value)
150  {
151    // ETags cannot be used in ordering matching.
152    return ConditionResult.UNDEFINED;
153  }
154
155
156
157  /** {@inheritDoc} */
158  @Override
159  public boolean hasValue(final Entry entry, final VirtualAttributeRule rule)
160  {
161    // ETag is always present.
162    return true;
163  }
164
165
166
167  /** {@inheritDoc} */
168  @Override
169  public void initializeVirtualAttributeProvider(
170      final EntityTagVirtualAttributeCfg configuration) throws ConfigException,
171      InitializationException
172  {
173    this.config = configuration;
174    configuration.addEntityTagChangeListener(this);
175  }
176
177
178
179  /** {@inheritDoc} */
180  @Override
181  public boolean isConfigurationChangeAcceptable(
182      final EntityTagVirtualAttributeCfg configuration,
183      final List<LocalizableMessage> unacceptableReasons)
184  {
185    // The new configuration should always be acceptable.
186    return true;
187  }
188
189
190
191  /** {@inheritDoc} */
192  @Override
193  public boolean isMultiValued()
194  {
195    // ETag is always single-valued.
196    return false;
197  }
198
199
200
201  /** {@inheritDoc} */
202  @Override
203  public boolean isSearchable(final VirtualAttributeRule rule,
204                              final SearchOperation searchOperation,
205                              final boolean isPreIndexed)
206  {
207    // ETags cannot be searched since there is no way to determine which entry
208    // is associated with a particular ETag.
209    return false;
210  }
211
212
213
214  /** {@inheritDoc} */
215  @Override
216  public ConditionResult lessThanOrEqualTo(final Entry entry,
217      final VirtualAttributeRule rule, final ByteString value)
218  {
219    // ETags cannot be used in ordering matching.
220    return ConditionResult.UNDEFINED;
221  }
222
223
224
225  /** {@inheritDoc} */
226  @Override
227  public ConditionResult matchesSubstring(final Entry entry,
228      final VirtualAttributeRule rule, final ByteString subInitial,
229      final List<ByteString> subAny, final ByteString subFinal)
230  {
231    // ETags cannot be used in substring matching.
232    return ConditionResult.UNDEFINED;
233  }
234
235
236
237  /** {@inheritDoc} */
238  @Override
239  public void processSearch(final VirtualAttributeRule rule,
240      final SearchOperation searchOperation)
241  {
242    final LocalizableMessage message = ERR_ETAG_VATTR_NOT_SEARCHABLE.get(rule
243        .getAttributeType().getNameOrOID());
244    searchOperation.appendErrorMessage(message);
245    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
246  }
247
248
249
250  private void checksumAttribute(final EntityTagVirtualAttributeCfg cfg,
251      final Checksum checksummer, final Attribute attribute)
252  {
253    // Object class may be null.
254    if (attribute == null)
255    {
256      return;
257    }
258
259    // Ignore other virtual attributes include this one.
260    if (attribute.isVirtual())
261    {
262      return;
263    }
264
265    // Ignore excluded attributes.
266    if (cfg.getExcludedAttribute().contains(attribute.getAttributeType()))
267    {
268      return;
269    }
270
271    // Checksum the attribute description.
272    final String atd = attribute.getNameWithOptions();
273    final byte[] bytes = StaticUtils.getBytes(atd);
274    checksummer.update(bytes, 0, bytes.length);
275
276    // Checksum the attribute values. The value order may vary between
277    // replicas so we need to make sure that we always process them in the
278    // same order. Note that we don't need to normalize the values since we want
279    // to detect any kind of updates even if they are not semantically
280    // significant. In any case, normalization can be expensive and should be
281    // avoided if possible.
282    final int size = attribute.size();
283    switch (size)
284    {
285    case 0:
286      // It's surprising to have an empty attribute, but if we do then there's
287      // nothing to do.
288      break;
289    case 1:
290      // Avoid sorting single valued attributes.
291      checksumValue(checksummer, attribute.iterator().next());
292      break;
293    default:
294      // Multi-valued attributes need sorting.
295      final ByteString[] values = new ByteString[size];
296      int i = 0;
297      for (final ByteString av : attribute)
298      {
299        values[i++] = av;
300      }
301      Arrays.sort(values);
302      for (final ByteString value : values)
303      {
304        checksumValue(checksummer, value);
305      }
306      break;
307    }
308  }
309
310
311
312  private ByteString checksumEntry(final EntityTagVirtualAttributeCfg cfg,
313      final Checksum checksummer, final Entry entry)
314  {
315    // Checksum the object classes since these are not included in the entry's
316    // attributes.
317    checksumAttribute(cfg, checksummer, entry.getObjectClassAttribute());
318
319    // The attribute order may vary between replicas so we need to make sure
320    // that we always process them in the same order.
321    final List<Attribute> attributes = entry.getAttributes();
322    Collections.sort(attributes, ATTRIBUTE_COMPARATOR);
323    for (final Attribute attribute : attributes)
324    {
325      checksumAttribute(cfg, checksummer, attribute);
326    }
327
328    // Convert the checksum value to a hex string.
329    long checksum = checksummer.getValue();
330    final byte[] bytes = new byte[16];
331    int j = 15;
332    for (int i = 7; i >= 0; i--)
333    {
334      final byte b = (byte) (checksum & 0xFF);
335
336      final byte l = (byte) (b & 0x0F);
337      bytes[j--] = (byte) (l < 10 ? l + 48 : l + 87);
338
339      final byte h = (byte) ((b & 0xF0) >>> 4);
340      bytes[j--] = (byte) (h < 10 ? h + 48 : h + 87);
341
342      checksum >>>= 8;
343    }
344    return ByteString.wrap(bytes);
345  }
346
347
348
349  private void checksumValue(final Checksum checksummer, final ByteString value)
350  {
351    final int size = value.length();
352    for (int i = 0; i < size; i++)
353    {
354      checksummer.update(value.byteAt(i) & 0xFF);
355    }
356  }
357}