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}