opends/resource/config/config.ldif
@@ -21,7 +21,7 @@ # CDDL HEADER END # # Copyright 2006-2010 Sun Microsystems, Inc. # Portions Copyright 2010-2011 ForgeRock AS. # Portions Copyright 2010-2012 ForgeRock AS. # # # This file contains the primary Directory Server configuration. It must not @@ -2488,6 +2488,18 @@ ds-cfg-attribute-type: pwdPolicySubentry ds-cfg-conflict-behavior: virtual-overrides-real dn: cn=etag,cn=Virtual Attributes,cn=config objectClass: top objectClass: ds-cfg-virtual-attribute objectClass: ds-cfg-entity-tag-virtual-attribute cn: etag ds-cfg-java-class: org.opends.server.extensions.EntityTagVirtualAttributeProvider ds-cfg-enabled: true ds-cfg-attribute-type: etag ds-cfg-conflict-behavior: real-overrides-virtual ds-cfg-checksum-algorithm: adler-32 ds-cfg-excluded-attribute: ds-sync-hist dn: cn=Work Queue,cn=config objectClass: top objectClass: ds-cfg-work-queue opends/resource/schema/00-core.ldif
@@ -22,7 +22,7 @@ # # # Copyright 2006-2010 Sun Microsystems, Inc. # Portions Copyright 2011 ForgeRock AS # Portions Copyright 2011-2012 ForgeRock AS # # # This file contains a core set of attribute type and objectlass definitions @@ -519,6 +519,12 @@ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'draft-good-ldap-changelog' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.59 NAME 'etag' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass X-ORIGIN 'RFC 4512' ) objectClasses: ( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName opends/resource/schema/02-config.ldif
@@ -3306,6 +3306,16 @@ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.57 NAME 'ds-cfg-excluded-attribute' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.58 NAME 'ds-cfg-checksum-algorithm' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top @@ -5103,4 +5113,10 @@ ds-cfg-search-response-nentries-less-than $ ds-cfg-search-response-is-indexed ) X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.8 NAME 'ds-cfg-entity-tag-virtual-attribute' SUP ds-cfg-virtual-attribute STRUCTURAL MAY ( ds-cfg-checksum-algorithm $ ds-cfg-excluded-attribute ) X-ORIGIN 'OpenDJ Directory Server' ) opends/src/admin/defn/org/opends/server/admin/std/EntityTagVirtualAttributeConfiguration.xml
New file @@ -0,0 +1,125 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- ! CDDL HEADER START ! ! The contents of this file are subject to the terms of the ! Common Development and Distribution License, Version 1.0 only ! (the "License"). You may not use this file except in compliance ! with the License. ! ! You can obtain a copy of the license at ! trunk/opends/resource/legal-notices/OpenDS.LICENSE ! or https://OpenDS.dev.java.net/OpenDS.LICENSE. ! See the License for the specific language governing permissions ! and limitations under the License. ! ! When distributing Covered Code, include this CDDL HEADER in each ! file and include the License file at ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, ! add the following below this CDDL HEADER, with the fields enclosed ! by brackets "[]" replaced with your own identifying information: ! Portions Copyright [yyyy] [name of copyright owner] ! ! CDDL HEADER END ! ! ! Copyright 2012 ForgeRock AS ! --> <adm:managed-object name="entity-tag-virtual-attribute" plural-name="entity-tag-virtual-attributes" package="org.opends.server.admin.std" extends="virtual-attribute" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> The <adm:user-friendly-name /> ensures that all entries contain an "entity tag" or "Etag" as defined in section 3.11 of RFC 2616. </adm:synopsis> <adm:description> The entity tag may be used by clients, in conjunction with the assertion control, for optimistic concurrency control, as a way to help prevent simultaneous updates of an entry from conflicting with each other. </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-entity-tag-virtual-attribute</ldap:name> <ldap:superior>ds-cfg-virtual-attribute</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-class" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value> org.opends.server.extensions.EntityTagVirtualAttributeProvider </adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property-override name="conflict-behavior" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value>real-overrides-virtual</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property-override name="attribute-type"> <adm:default-behavior> <adm:defined> <adm:value>etag</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="checksum-algorithm"> <adm:synopsis> The algorithm which should be used for calculating the entity tag checksum value. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>adler-32</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:enumeration> <adm:value name="adler-32"> <adm:synopsis> The Adler-32 checksum algorithm which is almost as reliable as a CRC-32 but can be computed much faster. </adm:synopsis> </adm:value> <adm:value name="crc-32"> <adm:synopsis> The CRC-32 checksum algorithm. </adm:synopsis> </adm:value> </adm:enumeration> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-checksum-algorithm</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="excluded-attribute" multi-valued="true"> <adm:synopsis> The list of attributes which should be ignored when calculating the entity tag checksum value. </adm:synopsis> <adm:description> Certain attributes like "ds-sync-hist" may vary between replicas due to different purging schedules and should not be included in the checksum. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>ds-sync-hist</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:attribute-type /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-excluded-attribute</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/admin/messages/EntityTagVirtualAttributeCfgDefn.properties
New file @@ -0,0 +1,30 @@ user-friendly-name=Entity Tag Virtual Attribute user-friendly-plural-name=Entity Tag Virtual Attributes synopsis=The Entity Tag Virtual Attribute ensures that all entries contain an "entity tag" or "Etag" as defined in section 3.11 of RFC 2616. description=The entity tag may be used by clients, in conjunction with the assertion control, for optimistic concurrency control, as a way to help prevent simultaneous updates of an entry from conflicting with each other. property.attribute-type.synopsis=Specifies the attribute type for the attribute whose values are to be dynamically assigned by the virtual attribute. property.base-dn.synopsis=Specifies the base DNs for the branches containing entries that are eligible to use this virtual attribute. property.base-dn.description=If no values are given, then the server generates virtual attributes anywhere in the server. property.base-dn.default-behavior.alias.synopsis=The location of the entry in the server is not taken into account when determining whether an entry is eligible to use this virtual attribute. property.checksum-algorithm.synopsis=The algorithm which should be used for calculating the entity tag checksum value. property.checksum-algorithm.syntax.enumeration.value.adler-32.synopsis=The Adler-32 checksum algorithm which is almost as reliable as a CRC-32 but can be computed much faster. property.checksum-algorithm.syntax.enumeration.value.crc-32.synopsis=The CRC-32 checksum algorithm. property.conflict-behavior.synopsis=Specifies the behavior that the server is to exhibit for entries that already contain one or more real values for the associated attribute. property.conflict-behavior.syntax.enumeration.value.merge-real-and-virtual.synopsis=Indicates that the virtual attribute provider is to preserve any real values contained in the entry and merge them with the set of generated virtual values so that both the real and virtual values are used. property.conflict-behavior.syntax.enumeration.value.real-overrides-virtual.synopsis=Indicates that any real values contained in the entry are preserved and used, and virtual values are not generated. property.conflict-behavior.syntax.enumeration.value.virtual-overrides-real.synopsis=Indicates that the virtual attribute provider suppresses any real values contained in the entry and generates virtual values and uses them. property.enabled.synopsis=Indicates whether the Entity Tag Virtual Attribute is enabled for use. property.excluded-attribute.synopsis=The list of attributes which should be ignored when calculating the entity tag checksum value. property.excluded-attribute.description=Certain attributes like "ds-sync-hist" may vary between replicas due to different purging schedules and should not be included in the checksum. property.filter.synopsis=Specifies the search filters to be applied against entries to determine if the virtual attribute is to be generated for those entries. property.filter.description=If no values are given, then any entry is eligible to have the value generated. If one or more filters are specified, then only entries that match at least one of those filters are allowed to have the virtual attribute. property.filter.syntax.string.pattern.synopsis=Any valid search filter string. property.group-dn.synopsis=Specifies the DNs of the groups whose members can be eligible to use this virtual attribute. property.group-dn.description=If no values are given, then group membership is not taken into account when generating the virtual attribute. If one or more group DNs are specified, then only members of those groups are allowed to have the virtual attribute. property.group-dn.default-behavior.alias.synopsis=Group membership is not taken into account when determining whether an entry is eligible to use this virtual attribute. property.java-class.synopsis=Specifies the fully-qualified name of the virtual attribute provider class that generates the attribute values. property.scope.synopsis=Specifies the LDAP scope associated with base DNs for entries that are eligible to use this virtual attribute. property.scope.syntax.enumeration.value.base-object.synopsis=Search the base object only. property.scope.syntax.enumeration.value.single-level.synopsis=Search the immediate children of the base object but do not include any of their descendants or the base object itself. property.scope.syntax.enumeration.value.subordinate-subtree.synopsis=Search the entire subtree below the base object but do not include the base object itself. property.scope.syntax.enumeration.value.whole-subtree.synopsis=Search the base object and the entire subtree below the base object. opends/src/messages/messages/extension.properties
@@ -21,7 +21,7 @@ # CDDL HEADER END # # Copyright 2006-2010 Sun Microsystems, Inc. # Portions Copyright 2011 ForgeRock AS # Portions Copyright 2011-2012 ForgeRock AS @@ -1516,4 +1516,5 @@ SEVERE_ERR_LDAP_PTA_NO_PWD_613=The configuration of LDAP PTA policy \ "%s" is invalid because it does not specify the a means for obtaining the mapped \ search bind password MILD_ERR_ETAG_VATTR_NOT_SEARCHABLE_614=The %s attribute is not \ searchable and should not be included in otherwise unindexed search filters opends/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProvider.java
New file @@ -0,0 +1,381 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2012 ForgeRock AS. */ package org.opends.server.extensions; import static org.opends.messages.ExtensionMessages.*; import java.util.*; import java.util.zip.Adler32; import java.util.zip.CRC32; import java.util.zip.Checksum; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.EntityTagVirtualAttributeCfg; import org.opends.server.api.VirtualAttributeProvider; import org.opends.server.config.ConfigException; import org.opends.server.core.SearchOperation; import org.opends.server.types.*; import org.opends.server.util.StaticUtils; /** * This class implements a virtual attribute provider which ensures that all * entries contain an "entity tag" or "Etag" as defined in section 3.11 of RFC * 2616. * <p> * The entity tag may be used by clients, in conjunction with the assertion * control, for optimistic concurrency control, as a way to help prevent * simultaneous updates of an entry from conflicting with each other. */ public final class EntityTagVirtualAttributeProvider extends VirtualAttributeProvider<EntityTagVirtualAttributeCfg> implements ConfigurationChangeListener<EntityTagVirtualAttributeCfg> { private static final Comparator<Attribute> ATTRIBUTE_COMPARATOR = new Comparator<Attribute>() { /** * {@inheritDoc} */ public int compare(final Attribute a1, final Attribute a2) { return a1.getNameWithOptions().compareTo(a2.getNameWithOptions()); } }; // Current configuration. private volatile EntityTagVirtualAttributeCfg config; /** * Default constructor invoked by reflection. */ public EntityTagVirtualAttributeProvider() { // Initialization performed by initializeVirtualAttributeProvider. } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( final EntityTagVirtualAttributeCfg configuration) { this.config = configuration; return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ @Override() public ConditionResult approximatelyEqualTo(final Entry entry, final VirtualAttributeRule rule, final AttributeValue value) { // ETags cannot be used in approximate matching. return ConditionResult.UNDEFINED; } /** * {@inheritDoc} */ @Override() public void finalizeVirtualAttributeProvider() { config.removeEntityTagChangeListener(this); } /** * {@inheritDoc} */ @Override() public Set<AttributeValue> getValues(final Entry entry, final VirtualAttributeRule rule) { // Save reference to current configuration in case it changes. final EntityTagVirtualAttributeCfg cfg = config; // Determine which checksum algorithm to use. final Checksum checksummer; switch (cfg.getChecksumAlgorithm()) { case CRC_32: checksummer = new CRC32(); break; default: // ADLER_32 checksummer = new Adler32(); break; } final ByteString etag = checksumEntry(cfg, checksummer, entry); final AttributeValue value = AttributeValues.create(etag, etag); return Collections.singleton(value); } /** * {@inheritDoc} */ @Override() public ConditionResult greaterThanOrEqualTo(final Entry entry, final VirtualAttributeRule rule, final AttributeValue value) { // ETags cannot be used in ordering matching. return ConditionResult.UNDEFINED; } /** * {@inheritDoc} */ @Override() public boolean hasValue(final Entry entry, final VirtualAttributeRule rule) { // ETag is always present. return true; } /** * {@inheritDoc} */ @Override() public void initializeVirtualAttributeProvider( final EntityTagVirtualAttributeCfg configuration) throws ConfigException, InitializationException { this.config = configuration; configuration.addEntityTagChangeListener(this); } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable( final EntityTagVirtualAttributeCfg configuration, final List<Message> unacceptableReasons) { // The new configuration should always be acceptable. return true; } /** * {@inheritDoc} */ @Override() public boolean isMultiValued() { // ETag is always single-valued. return false; } /** * {@inheritDoc}. */ @Override() public boolean isSearchable(final VirtualAttributeRule rule, final SearchOperation searchOperation) { // ETags cannot be searched since there is no way to determine which entry // is associated with a particular ETag. return false; } /** * {@inheritDoc} */ @Override() public ConditionResult lessThanOrEqualTo(final Entry entry, final VirtualAttributeRule rule, final AttributeValue value) { // ETags cannot be used in ordering matching. return ConditionResult.UNDEFINED; } /** * {@inheritDoc} */ @Override() public ConditionResult matchesSubstring(final Entry entry, final VirtualAttributeRule rule, final ByteString subInitial, final List<ByteString> subAny, final ByteString subFinal) { // ETags cannot be used in substring matching. return ConditionResult.UNDEFINED; } /** * {@inheritDoc} */ @Override() public void processSearch(final VirtualAttributeRule rule, final SearchOperation searchOperation) { final Message message = ERR_ETAG_VATTR_NOT_SEARCHABLE.get(rule .getAttributeType().getNameOrOID()); searchOperation.appendErrorMessage(message); searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM); } private void checksumAttribute(final EntityTagVirtualAttributeCfg cfg, final Checksum checksummer, final Attribute attribute) { // Object class may be null. if (attribute == null) { return; } // Ignore other virtual attributes include this one. if (attribute.isVirtual()) { return; } // Ignore excluded attributes. if (cfg.getExcludedAttribute().contains(attribute.getAttributeType())) { return; } // Checksum the attribute description. final String atd = attribute.getNameWithOptions(); final byte[] bytes = StaticUtils.getBytes(atd); checksummer.update(bytes, 0, bytes.length); // Checksum the attribute values. The value order may vary between // replicas so we need to make sure that we always process them in the // same order. Note that we don't need to normalize the values since we want // to detect any kind of updates even if they are not semantically // significant. In any case, normalization can be expensive and should be // avoided if possible. final int size = attribute.size(); switch (size) { case 0: // It's surprising to have an empty attribute, but if we do then there's // nothing to do. break; case 1: // Avoid sorting single valued attributes. checksumValue(checksummer, attribute.iterator().next().getValue()); break; default: // Multi-valued attributes need sorting. final ByteString[] values = new ByteString[size]; int i = 0; for (final AttributeValue av : attribute) { values[i++] = av.getValue(); } Arrays.sort(values); for (final ByteString value : values) { checksumValue(checksummer, value); } break; } } private ByteString checksumEntry(final EntityTagVirtualAttributeCfg cfg, final Checksum checksummer, final Entry entry) { // Checksum the object classes since these are not included in the entry's // attributes. checksumAttribute(cfg, checksummer, entry.getObjectClassAttribute()); // The attribute order may vary between replicas so we need to make sure // that we always process them in the same order. final List<Attribute> attributes = entry.getAttributes(); Collections.sort(attributes, ATTRIBUTE_COMPARATOR); for (final Attribute attribute : attributes) { checksumAttribute(cfg, checksummer, attribute); } // Convert the checksum value to a hex string. long checksum = checksummer.getValue(); final byte[] bytes = new byte[16]; int j = 15; for (int i = 7; i >= 0; i--) { final byte b = (byte) (checksum & 0xFF); final byte l = (byte) (b & 0x0F); bytes[j--] = (byte) (l < 10 ? l + 48 : l + 87); final byte h = (byte) ((b & 0xF0) >>> 4); bytes[j--] = (byte) (h < 10 ? h + 48 : h + 87); checksum >>>= 8; } return ByteString.wrap(bytes); } private void checksumValue(final Checksum checksummer, final ByteString value) { final int size = value.length(); for (int i = 0; i < size; i++) { checksummer.update(value.byteAt(i) & 0xFF); } } } opends/src/server/org/opends/server/types/Entry.java
@@ -23,7 +23,7 @@ * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions copyright 2011 ForgeRock AS * Portions copyright 2011-2012 ForgeRock AS */ package org.opends.server.types; import org.opends.messages.Message; @@ -390,7 +390,9 @@ */ public List<Attribute> getAttributes() { ArrayList<Attribute> attributes = new ArrayList<Attribute>(); // Estimate the size. int size = userAttributes.size() + operationalAttributes.size(); ArrayList<Attribute> attributes = new ArrayList<Attribute>(size); for (List<Attribute> list : userAttributes.values()) { opends/src/server/org/opends/server/types/VirtualAttribute.java
@@ -23,6 +23,7 @@ * * * Copyright 2008-2009 Sun Microsystems, Inc. * Portions copyright 2012 ForgeRock AS. */ package org.opends.server.types; @@ -278,7 +279,14 @@ */ public int size() { return provider.getValues(entry, rule).size(); if (!provider.isMultiValued()) { return provider.hasValue(entry, rule) ? 1 : 0; } else { return provider.getValues(entry, rule).size(); } } opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntityTagVirtualAttributeProviderTestCase.java
New file @@ -0,0 +1,580 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2012 ForgeRock AS */ package org.opends.server.extensions; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import java.util.*; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import org.opends.server.TestCaseUtils; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn; import org.opends.server.admin.std.meta.EntityTagVirtualAttributeCfgDefn.ChecksumAlgorithm; import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn.ConflictBehavior; import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn.Scope; import org.opends.server.admin.std.server.EntityTagVirtualAttributeCfg; import org.opends.server.admin.std.server.VirtualAttributeCfg; import org.opends.server.core.DirectoryServer; import org.opends.server.core.SearchOperation; import org.opends.server.core.SearchOperationWrapper; import org.opends.server.types.*; import org.opends.server.util.StaticUtils; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** * A set of test cases for the entity tag virtual attribute provider. */ public class EntityTagVirtualAttributeProviderTestCase extends ExtensionsTestCase { private final AttributeValue dummyValue = AttributeValues.create( ByteString.valueOf("dummy"), ByteString.valueOf("dummy")); private final EntityTagVirtualAttributeProvider provider = new EntityTagVirtualAttributeProvider(); private boolean changeListenerRemoved = false; private boolean changeListenerAdded = false; private final EntityTagVirtualAttributeCfg config = new EntityTagVirtualAttributeCfg() { private final TreeSet<AttributeType> excludedAttributes = new TreeSet<AttributeType>(); public void addChangeListener( final ConfigurationChangeListener<VirtualAttributeCfg> listener) { // Should not be called. throw new IllegalStateException(); } public void addEntityTagChangeListener( final ConfigurationChangeListener<EntityTagVirtualAttributeCfg> listener) { changeListenerAdded = true; } public Class<? extends EntityTagVirtualAttributeCfg> configurationClass() { // Not needed. return null; } public DN dn() { // Not needed. return null; } public AttributeType getAttributeType() { // Not needed. return null; } public SortedSet<DN> getBaseDN() { // Not needed. return null; } public ChecksumAlgorithm getChecksumAlgorithm() { return ChecksumAlgorithm.ADLER_32; } public ConflictBehavior getConflictBehavior() { // Not needed. return null; } public SortedSet<AttributeType> getExcludedAttribute() { return excludedAttributes; } public SortedSet<String> getFilter() { // Not needed. return null; } public SortedSet<DN> getGroupDN() { // Not needed. return null; } public String getJavaClass() { // Not needed. return null; } public Scope getScope() { // Not needed. return null; } public boolean isEnabled() { return true; } public void removeChangeListener( final ConfigurationChangeListener<VirtualAttributeCfg> listener) { // Should not be called. throw new IllegalStateException(); } public void removeEntityTagChangeListener( final ConfigurationChangeListener<EntityTagVirtualAttributeCfg> listener) { changeListenerRemoved = true; } }; /** * Ensures that the Directory Server is running. * * @throws Exception * If an unexpected problem occurs. */ @BeforeClass() public void startServer() throws Exception { TestCaseUtils.startServer(); // Initialize the provider. config.getExcludedAttribute().add( DirectoryServer.getAttributeType("modifytimestamp")); provider.initializeVirtualAttributeProvider(config); } /** * Tests that approximate matching is not supported. */ @Test public void testApproximatelyEqualTo() { assertEquals(provider.approximatelyEqualTo(null, null, null), ConditionResult.UNDEFINED); } /** * Tests that finalization removes the change listener. */ @Test public void testFinalizeVirtualAttributeProvider() { provider.finalizeVirtualAttributeProvider(); assertTrue(changeListenerRemoved); } /** * Tests the getValues method returns an ETag whose value represents a 64-bit * non-zero long encoded as hex. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testGetValuesBasic() throws Exception { final Entry e = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); getEntityTag(e); } /** * Tests the getValues method returns a different value for entries which are * different. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testGetValuesDifferent() throws Exception { final Entry e1 = TestCaseUtils.makeEntry("dn: dc=example1,dc=com", "objectClass: top", "objectClass: domain", "dc: example1"); final Entry e2 = TestCaseUtils.makeEntry("dn: dc=example2,dc=com", "objectClass: top", "objectClass: domain", "dc: example2"); assertFalse(getEntityTag(e1).equals(getEntityTag(e2))); } /** * Tests the getValues method ignores excluded attributes. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testGetValuesIgnoresExcludedAttributes() throws Exception { final Entry e1 = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); final Entry e2 = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example", "modifyTimestamp: 20120222232918Z"); assertEquals(getEntityTag(e1), getEntityTag(e2)); } /** * Tests the getValues method returns the same value for entries having the * same content but with attributes in a different order. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testGetValuesNormalizedOrder() throws Exception { final Entry e1 = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "description: one", "description: two", "dc: example"); final Entry e2 = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example", "description: two", "description: one"); assertEquals(getEntityTag(e1), getEntityTag(e2)); } /** * Tests the getValues method returns the same value for different instances * of the same entry. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testGetValuesRepeatable() throws Exception { final Entry e1 = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); final Entry e2 = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); assertEquals(getEntityTag(e1), getEntityTag(e2)); } /** * Tests that ordering matching is not supported. */ @Test public void testGreaterThanOrEqualTo() { assertEquals(provider.greaterThanOrEqualTo(null, null, null), ConditionResult.UNDEFINED); } /** * Tests hasAllValues() membership. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testHasAllValues() throws Exception { final Entry e = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); final AttributeValue value = getEntityTag(e); assertTrue(provider.hasAllValues(e, null, Collections.<AttributeValue> emptySet())); assertTrue(provider.hasAllValues(e, null, Collections.singleton(value))); assertFalse(provider.hasAllValues(e, null, Collections.singleton(dummyValue))); assertFalse(provider .hasAllValues(e, null, Arrays.asList(value, dummyValue))); } /** * Tests hasAnyValues() membership. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testHasAnyValue() throws Exception { final Entry e = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); final AttributeValue value = getEntityTag(e); assertTrue(provider.hasAnyValue(e, null, Collections.singleton(value))); assertFalse(provider .hasAnyValue(e, null, Collections.singleton(dummyValue))); assertTrue(provider.hasAnyValue(e, null, Arrays.asList(value, dummyValue))); } /** * Tests that the etags are always present. */ @Test public void testHasValue1() { assertTrue(provider.hasValue(null, null)); } /** * Tests testHasValue membership. * * @throws Exception * If an unexpected exception occurred. */ @Test public void testHasValue2() throws Exception { final Entry e = TestCaseUtils.makeEntry("dn: dc=example,dc=com", "objectClass: top", "objectClass: domain", "dc: example"); final AttributeValue value = getEntityTag(e); assertTrue(provider.hasValue(e, null, value)); assertFalse(provider.hasValue(e, null, dummyValue)); } /** * Tests that initialization adds the change listener. */ @Test public void testInitializeVirtualAttributeProvider() { // This was actually done during initialization of this test. Check that the // listener was registered. assertTrue(changeListenerAdded); } /** * Tests that isConfigurationAcceptable always returns true. */ @Test public void testIsConfigurationAcceptable() { assertTrue(provider.isConfigurationAcceptable(config, null)); } /** * Tests that the etags are single-valued. */ @Test public void testIsMultiValued() { assertFalse(provider.isMultiValued()); } /** * Tests that searching based on etag filters is not supported. */ @Test public void testIsSearchable() { assertFalse(provider.isSearchable(null, null)); } /** * Tests that ordering matching is not supported. */ @Test public void testLessThanOrEqualTo() { assertEquals(provider.lessThanOrEqualTo(null, null, null), ConditionResult.UNDEFINED); } /** * Tests that substring matching is not supported. */ @Test public void testMatchesSubstring() { assertEquals(provider.matchesSubstring(null, null, null, null, null), ConditionResult.UNDEFINED); } /** * Tests that searching based on etag filters is not supported. */ @Test public void testProcessSearch() { final SearchOperation search = new SearchOperationWrapper(null) { ResultCode resultCode = null; MessageBuilder message = null; /** * {@inheritDoc} */ public void appendErrorMessage(final Message message) { this.message = new MessageBuilder(message); } /** * {@inheritDoc} */ public MessageBuilder getErrorMessage() { return message; } /** * @return the resultCode */ public ResultCode getResultCode() { return resultCode; } /** * {@inheritDoc} */ public void setResultCode(final ResultCode resultCode) { this.resultCode = resultCode; } }; VirtualAttributeRule rule = new VirtualAttributeRule( DirectoryServer.getAttributeType("etag"), provider, Collections.<DN> emptySet(), SearchScope.WHOLE_SUBTREE, Collections.<DN> emptySet(), Collections.<SearchFilter> emptySet(), VirtualAttributeCfgDefn.ConflictBehavior.REAL_OVERRIDES_VIRTUAL); provider.processSearch(rule, search); assertEquals(search.getResultCode(), ResultCode.UNWILLING_TO_PERFORM); assertNotNull(search.getErrorMessage()); } private AttributeValue getEntityTag(final Entry e) { final Set<AttributeValue> values = provider.getValues(e, null); assertEquals(values.size(), 1); final AttributeValue value = values.iterator().next(); final ByteString bs = value.getValue(); assertEquals(bs.length(), 16); boolean gotNonZeroByte = false; for (int i = 0; i < 16; i++) { assertTrue(StaticUtils.isHexDigit(bs.byteAt(i))); if (bs.byteAt(i) != 0x30) { gotNonZeroByte = true; } } assertTrue(gotNonZeroByte); return value; } }