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