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