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}