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}