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 * Copyright 2009 Sun Microsystems, Inc. 025 * Portions copyright 2012-2013 ForgeRock AS. 026 */ 027 028package org.forgerock.opendj.ldap.controls; 029 030import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAPASSERT_CONTROL_BAD_OID; 031import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAPASSERT_INVALID_CONTROL_VALUE; 032import static com.forgerock.opendj.ldap.CoreMessages.ERR_LDAPASSERT_NO_CONTROL_VALUE; 033import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; 034 035import java.io.IOException; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.opendj.io.ASN1; 039import org.forgerock.opendj.io.ASN1Reader; 040import org.forgerock.opendj.io.ASN1Writer; 041import org.forgerock.opendj.io.LDAP; 042import org.forgerock.opendj.ldap.ByteString; 043import org.forgerock.opendj.ldap.ByteStringBuilder; 044import org.forgerock.opendj.ldap.DecodeException; 045import org.forgerock.opendj.ldap.DecodeOptions; 046import org.forgerock.opendj.ldap.Filter; 047 048import org.forgerock.util.Reject; 049 050/** 051 * The assertion request control as defined in RFC 4528. The Assertion control 052 * allows a client to specify that a directory operation should only be 053 * processed if an assertion applied to the target entry of the operation is 054 * true. It can be used to construct "test and set", "test and clear", and other 055 * conditional operations. 056 * <p> 057 * The following excerpt shows how to check that no description exists on an 058 * entry before adding a description. 059 * 060 * <pre> 061 * Connection connection = ...; 062 * connection.bind(...); 063 * 064 * String entryDN = ...; 065 * ModifyRequest request = 066 * Requests.newModifyRequest(entryDN) 067 * .addControl(AssertionRequestControl.newControl( 068 * true, Filter.valueOf("!(description=*)"))) 069 * .addModification(ModificationType.ADD, "description", 070 * "Created using LDAP assertion control"); 071 * 072 * connection.modify(request); 073 * ... 074 * </pre> 075 * 076 * @see <a href="http://tools.ietf.org/html/rfc4528">RFC 4528 - Lightweight 077 * Directory Access Protocol (LDAP) Assertion Control </a> 078 */ 079public final class AssertionRequestControl implements Control { 080 /** 081 * The IANA-assigned OID for the LDAP assertion request control. 082 */ 083 public static final String OID = "1.3.6.1.1.12"; 084 085 /** 086 * A decoder which can be used for decoding the LDAP assertion request 087 * control. 088 */ 089 public static final ControlDecoder<AssertionRequestControl> DECODER = 090 new ControlDecoder<AssertionRequestControl>() { 091 092 public AssertionRequestControl decodeControl(final Control control, 093 final DecodeOptions options) throws DecodeException { 094 Reject.ifNull(control); 095 096 if (control instanceof AssertionRequestControl) { 097 return (AssertionRequestControl) control; 098 } 099 100 if (!control.getOID().equals(OID)) { 101 final LocalizableMessage message = 102 ERR_LDAPASSERT_CONTROL_BAD_OID.get(control.getOID(), OID); 103 throw DecodeException.error(message); 104 } 105 106 if (!control.hasValue()) { 107 // The response control must always have a value. 108 final LocalizableMessage message = ERR_LDAPASSERT_NO_CONTROL_VALUE.get(); 109 throw DecodeException.error(message); 110 } 111 112 try { 113 final ASN1Reader reader = ASN1.getReader(control.getValue()); 114 final Filter filter = LDAP.readFilter(reader); 115 return new AssertionRequestControl(control.isCritical(), filter); 116 } catch (final IOException e) { 117 throw DecodeException.error(ERR_LDAPASSERT_INVALID_CONTROL_VALUE 118 .get(getExceptionMessage(e)), e); 119 } 120 } 121 122 public String getOID() { 123 return OID; 124 } 125 }; 126 127 /** 128 * Creates a new assertion using the provided criticality and assertion 129 * filter. 130 * 131 * @param isCritical 132 * {@code true} if it is unacceptable to perform the operation 133 * without applying the semantics of this control, or 134 * {@code false} if it can be ignored. 135 * @param filter 136 * The assertion filter. 137 * @return The new control. 138 * @throws NullPointerException 139 * If {@code filter} was {@code null}. 140 */ 141 public static AssertionRequestControl newControl(final boolean isCritical, final Filter filter) { 142 return new AssertionRequestControl(isCritical, filter); 143 } 144 145 /** The assertion filter. */ 146 private final Filter filter; 147 148 private final boolean isCritical; 149 150 /** Prevent direct instantiation. */ 151 private AssertionRequestControl(final boolean isCritical, final Filter filter) { 152 Reject.ifNull(filter); 153 this.isCritical = isCritical; 154 this.filter = filter; 155 } 156 157 /** 158 * Returns the assertion filter. 159 * 160 * @return The assertion filter. 161 */ 162 public Filter getFilter() { 163 return filter; 164 } 165 166 /** {@inheritDoc} */ 167 public String getOID() { 168 return OID; 169 } 170 171 /** {@inheritDoc} */ 172 public ByteString getValue() { 173 final ByteStringBuilder buffer = new ByteStringBuilder(); 174 final ASN1Writer writer = ASN1.getWriter(buffer); 175 try { 176 LDAP.writeFilter(writer, filter); 177 return buffer.toByteString(); 178 } catch (final IOException ioe) { 179 // This should never happen unless there is a bug somewhere. 180 throw new RuntimeException(ioe); 181 } 182 } 183 184 /** {@inheritDoc} */ 185 public boolean hasValue() { 186 return true; 187 } 188 189 /** {@inheritDoc} */ 190 public boolean isCritical() { 191 return isCritical; 192 } 193 194 /** {@inheritDoc} */ 195 @Override 196 public String toString() { 197 final StringBuilder builder = new StringBuilder(); 198 builder.append("AssertionRequestControl(oid="); 199 builder.append(getOID()); 200 builder.append(", criticality="); 201 builder.append(isCritical()); 202 builder.append(", filter=\""); 203 builder.append(filter); 204 builder.append("\")"); 205 return builder.toString(); 206 } 207}