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