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 2013-2014 ForgeRock AS. 026 */ 027 028package org.forgerock.opendj.ldap.schema; 029 030import static com.forgerock.opendj.ldap.CoreMessages.*; 031 032import java.util.Collections; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Map; 036import java.util.regex.Pattern; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.forgerock.i18n.LocalizableMessageBuilder; 040import org.forgerock.opendj.ldap.ByteSequence; 041import org.forgerock.util.Reject; 042 043/** 044 * This class defines a data structure for storing and interacting with an LDAP 045 * syntaxes, which constrain the structure of attribute values stored in an LDAP 046 * directory, and determine the representation of attribute and assertion values 047 * transferred in the LDAP protocol. 048 * <p> 049 * Syntax implementations must extend the {@link SyntaxImpl} interface so they 050 * can be used by OpenDJ to validate attribute values. 051 * <p> 052 * Where ordered sets of names, or extra properties are provided, the ordering 053 * will be preserved when the associated fields are accessed via their getters 054 * or via the {@link #toString()} methods. 055 */ 056public final class Syntax extends SchemaElement { 057 058 /** A fluent API for incrementally constructing syntaxes. */ 059 public static final class Builder extends SchemaElementBuilder<Builder> { 060 061 private String oid; 062 private SyntaxImpl impl; 063 064 Builder(final Syntax syntax, final SchemaBuilder builder) { 065 super(builder, syntax); 066 this.oid = syntax.oid; 067 this.impl = syntax.impl; 068 } 069 070 Builder(final String oid, final SchemaBuilder builder) { 071 super(builder); 072 oid(oid); 073 } 074 075 /** 076 * Adds this syntax to the schema, throwing a 077 * {@code ConflictingSchemaElementException} if there is an existing 078 * syntax with the same numeric OID. 079 * 080 * @return The parent schema builder. 081 * @throws ConflictingSchemaElementException 082 * If there is an existing syntax with the same numeric OID. 083 */ 084 public SchemaBuilder addToSchema() { 085 return getSchemaBuilder().addSyntax(new Syntax(this), false); 086 } 087 088 /** 089 * Adds this syntax to the schema overwriting any existing syntax with the same numeric OID. 090 * 091 * @return The parent schema builder. 092 */ 093 public SchemaBuilder addToSchemaOverwrite() { 094 return getSchemaBuilder().addSyntax(new Syntax(this), true); 095 } 096 097 /** 098 * Adds this syntax to the schema - overwriting any existing syntax with the same numeric OID 099 * if the overwrite parameter is set to {@code true}. 100 * 101 * @param overwrite 102 * {@code true} if any syntax with the same OID should be overwritten. 103 * @return The parent schema builder. 104 */ 105 SchemaBuilder addToSchema(final boolean overwrite) { 106 if (overwrite) { 107 return addToSchemaOverwrite(); 108 } 109 return addToSchema(); 110 } 111 112 @Override 113 public Builder description(final String description) { 114 return description0(description); 115 } 116 117 @Override 118 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 119 return extraProperties0(extraProperties); 120 } 121 122 @Override 123 public Builder extraProperties(final String extensionName, final String... extensionValues) { 124 return extraProperties0(extensionName, extensionValues); 125 } 126 127 /** 128 * Sets the numeric OID which uniquely identifies this syntax. 129 * 130 * @param oid 131 * The numeric OID. 132 * @return This builder. 133 */ 134 public Builder oid(final String oid) { 135 this.oid = oid; 136 return this; 137 } 138 139 @Override 140 public Builder removeAllExtraProperties() { 141 return removeAllExtraProperties0(); 142 } 143 144 @Override 145 public Builder removeExtraProperty(final String extensionName, final String... extensionValues) { 146 return removeExtraProperty0(extensionName, extensionValues); 147 } 148 149 /** 150 * Sets the syntax implementation. 151 * 152 * @param implementation 153 * The syntax implementation. 154 * @return This builder. 155 */ 156 public Builder implementation(final SyntaxImpl implementation) { 157 this.impl = implementation; 158 return this; 159 } 160 161 @Override 162 Builder getThis() { 163 return this; 164 } 165 } 166 167 private final String oid; 168 private MatchingRule equalityMatchingRule; 169 private MatchingRule orderingMatchingRule; 170 private MatchingRule substringMatchingRule; 171 private MatchingRule approximateMatchingRule; 172 private Schema schema; 173 private SyntaxImpl impl; 174 175 private Syntax(final Builder builder) { 176 super(builder); 177 178 // Checks for required attributes. 179 if (builder.oid == null || builder.oid.isEmpty()) { 180 throw new IllegalArgumentException("An OID must be specified."); 181 } 182 183 oid = builder.oid; 184 impl = builder.impl; 185 } 186 187 /** 188 * Creates a syntax representing an unrecognized syntax and whose 189 * implementation is substituted by the schema's default syntax. 190 * 191 * @param schema 192 * The parent schema. 193 * @param oid 194 * The numeric OID of the unrecognized syntax. 195 */ 196 Syntax(final Schema schema, final String oid) { 197 super("", Collections.singletonMap("X-SUBST", Collections.singletonList(schema.getDefaultSyntax().getOID())), 198 null); 199 200 Reject.ifNull(oid); 201 this.oid = oid; 202 this.schema = schema; 203 204 final Syntax defaultSyntax = schema.getDefaultSyntax(); 205 this.impl = defaultSyntax.impl; 206 this.approximateMatchingRule = defaultSyntax.getApproximateMatchingRule(); 207 this.equalityMatchingRule = defaultSyntax.getEqualityMatchingRule(); 208 this.orderingMatchingRule = defaultSyntax.getOrderingMatchingRule(); 209 this.substringMatchingRule = defaultSyntax.getSubstringMatchingRule(); 210 } 211 212 /** 213 * Returns {@code true} if the provided object is an attribute syntax having 214 * the same numeric OID as this attribute syntax. 215 * 216 * @param o 217 * The object to be compared. 218 * @return {@code true} if the provided object is an attribute syntax having 219 * the same numeric OID as this attribute syntax. 220 */ 221 @Override 222 public boolean equals(final Object o) { 223 if (this == o) { 224 return true; 225 } else if (o instanceof Syntax) { 226 final Syntax other = (Syntax) o; 227 return oid.equals(other.oid); 228 } else { 229 return false; 230 } 231 } 232 233 /** 234 * Retrieves the default approximate matching rule that will be used for 235 * attributes with this syntax. 236 * 237 * @return The default approximate matching rule that will be used for 238 * attributes with this syntax, or {@code null} if approximate 239 * matches will not be allowed for this type by default. 240 */ 241 public MatchingRule getApproximateMatchingRule() { 242 return approximateMatchingRule; 243 } 244 245 /** 246 * Retrieves the default equality matching rule that will be used for 247 * attributes with this syntax. 248 * 249 * @return The default equality matching rule that will be used for 250 * attributes with this syntax, or {@code null} if equality matches 251 * will not be allowed for this type by default. 252 */ 253 public MatchingRule getEqualityMatchingRule() { 254 return equalityMatchingRule; 255 } 256 257 /** 258 * Retrieves the OID for this attribute syntax. 259 * 260 * @return The OID for this attribute syntax. 261 */ 262 public String getOID() { 263 return oid; 264 } 265 266 /** 267 * Retrieves the name for this attribute syntax. 268 * 269 * @return The name for this attribute syntax. 270 */ 271 public String getName() { 272 return impl.getName(); 273 } 274 275 /** 276 * Retrieves the default ordering matching rule that will be used for 277 * attributes with this syntax. 278 * 279 * @return The default ordering matching rule that will be used for 280 * attributes with this syntax, or {@code null} if ordering matches 281 * will not be allowed for this type by default. 282 */ 283 public MatchingRule getOrderingMatchingRule() { 284 return orderingMatchingRule; 285 } 286 287 /** 288 * Retrieves the default substring matching rule that will be used for 289 * attributes with this syntax. 290 * 291 * @return The default substring matching rule that will be used for 292 * attributes with this syntax, or {@code null} if substring matches 293 * will not be allowed for this type by default. 294 */ 295 public MatchingRule getSubstringMatchingRule() { 296 return substringMatchingRule; 297 } 298 299 /** 300 * Returns the hash code for this attribute syntax. It will be calculated as 301 * the hash code of the numeric OID. 302 * 303 * @return The hash code for this attribute syntax. 304 */ 305 @Override 306 public int hashCode() { 307 return oid.hashCode(); 308 } 309 310 /** 311 * Indicates whether this attribute syntax requires that values must be 312 * encoded using the Basic Encoding Rules (BER) used by X.500 directories 313 * and always include the {@code binary} attribute description option. 314 * 315 * @return {@code true} this attribute syntax requires that values must be 316 * BER encoded and always include the {@code binary} attribute 317 * description option, or {@code false} if not. 318 * @see <a href="http://tools.ietf.org/html/rfc4522">RFC 4522 - Lightweight 319 * Directory Access Protocol (LDAP): The Binary Encoding Option </a> 320 */ 321 public boolean isBEREncodingRequired() { 322 return impl.isBEREncodingRequired(); 323 } 324 325 /** 326 * Indicates whether this attribute syntax would likely be a human readable 327 * string. 328 * 329 * @return {@code true} if this attribute syntax would likely be a human 330 * readable string or {@code false} if not. 331 */ 332 public boolean isHumanReadable() { 333 return impl.isHumanReadable(); 334 } 335 336 /** 337 * Indicates whether the provided value is acceptable for use in an 338 * attribute with this syntax. If it is not, then the reason may be appended 339 * to the provided buffer. 340 * 341 * @param value 342 * The value for which to make the determination. 343 * @param invalidReason 344 * The buffer to which the invalid reason should be appended. 345 * @return {@code true} if the provided value is acceptable for use with 346 * this syntax, or {@code false} if not. 347 */ 348 public boolean valueIsAcceptable(final ByteSequence value, 349 final LocalizableMessageBuilder invalidReason) { 350 return impl.valueIsAcceptable(schema, value, invalidReason); 351 } 352 353 @Override 354 void toStringContent(final StringBuilder buffer) { 355 buffer.append(oid); 356 appendDescription(buffer); 357 } 358 359 void validate(final Schema schema, final List<LocalizableMessage> warnings) 360 throws SchemaException { 361 this.schema = schema; 362 if (impl == null) { 363 // See if we need to override the implementation of the syntax 364 for (final Map.Entry<String, List<String>> property : getExtraProperties().entrySet()) { 365 // Enums are handled in the schema builder. 366 if ("x-subst".equalsIgnoreCase(property.getKey())) { 367 /** 368 * One unimplemented syntax can be substituted by another 369 * defined syntax. A substitution syntax is an 370 * LDAPSyntaxDescriptionSyntax with X-SUBST extension. 371 */ 372 final Iterator<String> values = property.getValue().iterator(); 373 if (values.hasNext()) { 374 final String value = values.next(); 375 if (value.equals(oid)) { 376 throw new SchemaException(ERR_ATTR_SYNTAX_CYCLIC_SUB_SYNTAX.get(oid)); 377 } 378 if (!schema.hasSyntax(value)) { 379 throw new SchemaException(ERR_ATTR_SYNTAX_UNKNOWN_SUB_SYNTAX.get(oid, value)); 380 } 381 final Syntax subSyntax = schema.getSyntax(value); 382 if (subSyntax.impl == null) { 383 // The substitution syntax was never validated. 384 subSyntax.validate(schema, warnings); 385 } 386 impl = subSyntax.impl; 387 } 388 } else if ("x-pattern".equalsIgnoreCase(property.getKey())) { 389 final Iterator<String> values = property.getValue().iterator(); 390 if (values.hasNext()) { 391 final String value = values.next(); 392 try { 393 final Pattern pattern = Pattern.compile(value); 394 impl = new RegexSyntaxImpl(pattern); 395 } catch (final Exception e) { 396 throw new SchemaException( 397 WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN.get(oid, value)); 398 } 399 } 400 } 401 } 402 403 // Try to find an implementation in the core schema 404 if (impl == null && Schema.getDefaultSchema().hasSyntax(oid)) { 405 impl = Schema.getDefaultSchema().getSyntax(oid).impl; 406 } 407 if (impl == null && Schema.getCoreSchema().hasSyntax(oid)) { 408 impl = Schema.getCoreSchema().getSyntax(oid).impl; 409 } 410 411 if (impl == null) { 412 final Syntax defaultSyntax = schema.getDefaultSyntax(); 413 if (defaultSyntax.impl == null) { 414 // The default syntax was never validated. 415 defaultSyntax.validate(schema, warnings); 416 } 417 impl = defaultSyntax.impl; 418 final LocalizableMessage message = WARN_ATTR_SYNTAX_NOT_IMPLEMENTED1.get(getDescription(), oid, schema 419 .getDefaultSyntax().getOID()); 420 warnings.add(message); 421 } 422 } 423 424 // Get references to the default matching rules. It will be ok 425 // if we can't find some. Just warn. 426 if (impl.getEqualityMatchingRule() != null) { 427 if (schema.hasMatchingRule(impl.getEqualityMatchingRule())) { 428 equalityMatchingRule = schema.getMatchingRule(impl.getEqualityMatchingRule()); 429 } else { 430 final LocalizableMessage message = 431 ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(impl 432 .getEqualityMatchingRule(), impl.getName()); 433 warnings.add(message); 434 } 435 } 436 437 if (impl.getOrderingMatchingRule() != null) { 438 if (schema.hasMatchingRule(impl.getOrderingMatchingRule())) { 439 orderingMatchingRule = schema.getMatchingRule(impl.getOrderingMatchingRule()); 440 } else { 441 final LocalizableMessage message = 442 ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(impl 443 .getOrderingMatchingRule(), impl.getName()); 444 warnings.add(message); 445 } 446 } 447 448 if (impl.getSubstringMatchingRule() != null) { 449 if (schema.hasMatchingRule(impl.getSubstringMatchingRule())) { 450 substringMatchingRule = schema.getMatchingRule(impl.getSubstringMatchingRule()); 451 } else { 452 final LocalizableMessage message = 453 ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(impl 454 .getSubstringMatchingRule(), impl.getName()); 455 warnings.add(message); 456 } 457 } 458 459 if (impl.getApproximateMatchingRule() != null) { 460 if (schema.hasMatchingRule(impl.getApproximateMatchingRule())) { 461 approximateMatchingRule = schema.getMatchingRule(impl.getApproximateMatchingRule()); 462 } else { 463 final LocalizableMessage message = 464 ERR_ATTR_SYNTAX_UNKNOWN_APPROXIMATE_MATCHING_RULE.get(impl 465 .getApproximateMatchingRule(), impl.getName()); 466 warnings.add(message); 467 } 468 } 469 } 470 471 /** 472 * Indicates if the syntax has been validated, which means it has a non-null 473 * schema. 474 * 475 * @return {@code true} if and only if this syntax has been validated 476 */ 477 boolean isValidated() { 478 return schema != null; 479 } 480}