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 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.types; 028 029import java.util.Collection; 030import java.util.Collections; 031import java.util.LinkedList; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.opendj.ldap.ResultCode; 039 040import static org.forgerock.util.Reject.*; 041import static org.opends.messages.SchemaMessages.*; 042import static org.opends.server.util.CollectionUtils.*; 043import static org.opends.server.util.ServerConstants.*; 044import static org.opends.server.util.StaticUtils.*; 045 046/** 047 * An abstract base class for LDAP schema definitions which contain an 048 * OID, optional names, description, an obsolete flag, and an optional 049 * set of extra properties. 050 * <p> 051 * This class defines common properties and behaviour of the various 052 * types of schema definitions (e.g. object class definitions, and 053 * attribute type definitions). 054 * <p> 055 * Any methods which accesses the set of names associated with this 056 * definition, will retrieve the primary name as the first name, 057 * regardless of whether or not it was contained in the original set 058 * of <code>names</code> passed to the constructor. 059 * <p> 060 * Where ordered sets of names, or extra properties are provided, the 061 * ordering will be preserved when the associated fields are accessed 062 * via their getters or via the {@link #toString()} methods. 063 * <p> 064 * Note that these schema elements are not completely immutable, as 065 * the set of extra properties for the schema element may be altered 066 * after the element is created. Among other things, this allows the 067 * associated schema file to be edited so that an element created over 068 * protocol may be associated with a particular schema file. 069 */ 070@org.opends.server.types.PublicAPI( 071 stability=org.opends.server.types.StabilityLevel.VOLATILE, 072 mayInstantiate=false, 073 mayExtend=false, 074 mayInvoke=true) 075public abstract class CommonSchemaElements implements SchemaFileElement { 076 /** Indicates whether this definition is declared "obsolete". */ 077 private final boolean isObsolete; 078 079 /** The hash code for this definition. */ 080 private final int hashCode; 081 082 /** The set of additional name-value pairs associated with this definition. */ 083 private final Map<String, List<String>> extraProperties; 084 085 /** 086 * The set of names for this definition, in a mapping between 087 * the all-lowercase form and the user-defined form. 088 */ 089 private final Map<String, String> names; 090 091 /** The description for this definition. */ 092 private final String description; 093 094 /** The OID that may be used to reference this definition. */ 095 private final String oid; 096 097 /** The primary name to use for this definition. */ 098 private final String primaryName; 099 100 /** The lower case name for this definition. */ 101 private final String lowerName; 102 103 /** 104 * Creates a new definition with the provided information. 105 * <p> 106 * If no <code>primaryName</code> is specified, but a set of 107 * <code>names</code> is specified, then the first name retrieved 108 * from the set of <code>names</code> will be used as the primary 109 * name. 110 * 111 * @param primaryName 112 * The primary name for this definition, or 113 * <code>null</code> if there is no primary name. 114 * @param names 115 * The full set of names for this definition, or 116 * <code>null</code> if there are no names. 117 * @param oid 118 * The OID for this definition (must not be 119 * <code>null</code>). 120 * @param description 121 * The description for the definition, or <code>null</code> 122 * if there is no description. 123 * @param isObsolete 124 * Indicates whether this definition is declared 125 * "obsolete". 126 * @param extraProperties 127 * A set of extra properties for this definition, or 128 * <code>null</code> if there are no extra properties. 129 * @throws NullPointerException 130 * If the provided OID was <code>null</code>. 131 */ 132 protected CommonSchemaElements(String primaryName, 133 Collection<String> names, String oid, String description, 134 boolean isObsolete, Map<String, List<String>> extraProperties) 135 throws NullPointerException { 136 // Make sure mandatory parameters are specified. 137 if (oid == null) { 138 throw new NullPointerException( 139 "No oid specified in constructor"); 140 } 141 142 this.oid = oid; 143 this.description = description; 144 this.isObsolete = isObsolete; 145 146 // Make sure we have a primary name if possible. 147 if (primaryName == null) { 148 if (names != null && !names.isEmpty()) { 149 this.primaryName = names.iterator().next(); 150 } else { 151 this.primaryName = null; 152 } 153 } else { 154 this.primaryName = primaryName; 155 } 156 this.lowerName = toLowerCase(primaryName); 157 158 // OPENDJ-1645: oid changes during server bootstrap, so prefer using name if available 159 hashCode = getNameOrOID().hashCode(); 160 161 // Construct the normalized attribute name mapping. 162 if (names != null) { 163 this.names = new LinkedHashMap<>(names.size()); 164 165 // Make sure the primary name is first (never null). 166 this.names.put(lowerName, this.primaryName); 167 168 // Add the remaining names in the order specified. 169 for (String name : names) { 170 this.names.put(toLowerCase(name), name); 171 } 172 } else if (this.primaryName != null) { 173 this.names = Collections.singletonMap(lowerName, this.primaryName); 174 } else { 175 this.names = Collections.emptyMap(); 176 } 177 178 // FIXME: should really be a deep-copy. 179 if (extraProperties != null) { 180 this.extraProperties = new LinkedHashMap<>(extraProperties); 181 } else { 182 this.extraProperties = Collections.emptyMap(); 183 } 184 } 185 186 /** 187 * Check if the extra schema properties contain safe filenames. 188 * 189 * @param extraProperties 190 * The schema properties to check. 191 * 192 * @throws DirectoryException 193 * If a provided value was unsafe. 194 */ 195 public static void checkSafeProperties(Map <String,List<String>> 196 extraProperties) 197 throws DirectoryException 198 { 199 // Check that X-SCHEMA-FILE doesn't contain unsafe characters 200 List<String> filenames = extraProperties.get(SCHEMA_PROPERTY_FILENAME); 201 if (filenames != null && !filenames.isEmpty()) { 202 String filename = filenames.get(0); 203 if (filename.indexOf('/') != -1 || filename.indexOf('\\') != -1) 204 { 205 LocalizableMessage message = ERR_ATTR_SYNTAX_ILLEGAL_X_SCHEMA_FILE.get(filename); 206 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 207 message); 208 } 209 } 210 } 211 212 /** 213 * Retrieves the primary name for this schema definition. 214 * 215 * @return The primary name for this schema definition, or 216 * <code>null</code> if there is no primary name. 217 */ 218 public final String getPrimaryName() { 219 return primaryName; 220 } 221 222 /** 223 * Retrieve the normalized primary name for this schema definition. 224 * 225 * @return Returns the normalized primary name for this attribute 226 * type, or <code>null</code> if there is no primary name. 227 */ 228 public final String getNormalizedPrimaryName() { 229 return lowerName; 230 } 231 232 /** 233 * Retrieves an iterable over the set of normalized names that may 234 * be used to reference this schema definition. The normalized form 235 * of an attribute name is defined as the user-defined name 236 * converted to lower-case. 237 * 238 * @return Returns an iterable over the set of normalized names that 239 * may be used to reference this schema definition. 240 */ 241 public final Set<String> getNormalizedNames() { 242 return names.keySet(); 243 } 244 245 /** 246 * Retrieves an iterable over the set of user-defined names that may 247 * be used to reference this schema definition. 248 * 249 * @return Returns an iterable over the set of user-defined names 250 * that may be used to reference this schema definition. 251 */ 252 public final Iterable<String> getUserDefinedNames() { 253 return names.values(); 254 } 255 256 /** 257 * Indicates whether this schema definition has the specified name. 258 * 259 * @param lowerName 260 * The lowercase name for which to make the determination. 261 * @return <code>true</code> if the specified name is assigned to 262 * this schema definition, or <code>false</code> if not. 263 */ 264 public final boolean hasName(String lowerName) { 265 return names.containsKey(lowerName); 266 } 267 268 /** 269 * Retrieves the OID for this schema definition. 270 * 271 * @return The OID for this schema definition. 272 */ 273 public final String getOID() { 274 return oid; 275 } 276 277 /** 278 * Retrieves the name or OID for this schema definition. If it has 279 * one or more names, then the primary name will be returned. If it 280 * does not have any names, then the OID will be returned. 281 * 282 * @return The name or OID for this schema definition. 283 */ 284 public final String getNameOrOID() { 285 if (primaryName != null) { 286 return primaryName; 287 } 288 // Guaranteed not to be null. 289 return oid; 290 } 291 292 /** 293 * Retrieves the normalized primary name or OID for this schema 294 * definition. If it does not have any names, then the OID will be 295 * returned. 296 * 297 * @return The name or OID for this schema definition. 298 */ 299 public final String getNormalizedPrimaryNameOrOID() { 300 if (lowerName != null) { 301 return lowerName; 302 } 303 // Guaranteed not to be null. 304 return oid; 305 } 306 307 /** 308 * Indicates whether this schema definition has the specified name 309 * or OID. 310 * 311 * @param lowerValue 312 * The lowercase value for which to make the determination. 313 * @return <code>true</code> if the provided value matches the OID 314 * or one of the names assigned to this schema definition, 315 * or <code>false</code> if not. 316 */ 317 public final boolean hasNameOrOID(String lowerValue) { 318 return names.containsKey(lowerValue) || oid.equals(lowerValue); 319 } 320 321 /** 322 * Retrieves the name of the schema file that contains the 323 * definition for this schema definition. 324 * 325 * @param elem The element where to get the schema file from 326 * @return The name of the schema file that contains the definition 327 * for this schema definition, or <code>null</code> if it 328 * is not known or if it is not stored in any schema file. 329 */ 330 public static String getSchemaFile(SchemaFileElement elem) 331 { 332 return getSingleValueProperty(elem, SCHEMA_PROPERTY_FILENAME); 333 } 334 335 /** 336 * Retrieves the name of a single value property for this schema element. 337 * 338 * @param elem The element where to get the single value property from 339 * @param propertyName The name of the property to get 340 * @return The single value for this property, or <code>null</code> if it 341 * is this property is not set. 342 */ 343 public static String getSingleValueProperty(SchemaFileElement elem, 344 String propertyName) 345 { 346 List<String> values = elem.getExtraProperties().get(propertyName); 347 if (values != null && !values.isEmpty()) { 348 return values.get(0); 349 } 350 return null; 351 } 352 353 /** 354 * Specifies the name of the schema file that contains the 355 * definition for this schema element. If a schema file is already 356 * defined in the set of extra properties, then it will be 357 * overwritten. If the provided schema file value is {@code null}, 358 * then any existing schema file definition will be removed. 359 * 360 * @param elem The element where to set the schema file 361 * @param schemaFile The name of the schema file that contains the 362 * definition for this schema element. 363 */ 364 public static void setSchemaFile(SchemaFileElement elem, String schemaFile) 365 { 366 setExtraProperty(elem, SCHEMA_PROPERTY_FILENAME, schemaFile); 367 } 368 369 /** 370 * Retrieves the description for this schema definition. 371 * 372 * @return The description for this schema definition, or 373 * <code>null</code> if there is no description. 374 */ 375 public final String getDescription() { 376 return description; 377 } 378 379 /** 380 * Indicates whether this schema definition is declared "obsolete". 381 * 382 * @return <code>true</code> if this schema definition is declared 383 * "obsolete", or <code>false</code> if not. 384 */ 385 public final boolean isObsolete() { 386 return isObsolete; 387 } 388 389 @Override 390 public final Map<String, List<String>> getExtraProperties() 391 { 392 return extraProperties; 393 } 394 395 /** 396 * Sets the value for an "extra" property for this schema element. 397 * If a property already exists with the specified name, then it 398 * will be overwritten. If the value is {@code null}, then any 399 * existing property with the given name will be removed. 400 * 401 * @param elem The element where to set the extra property 402 * @param name The name for the "extra" property. It must not be 403 * {@code null}. 404 * @param value The value for the "extra" property. If it is 405 * {@code null}, then any existing definition will be removed. 406 */ 407 public static void setExtraProperty(SchemaFileElement elem, 408 String name, String value) 409 { 410 ifNull(name); 411 412 if (value == null) 413 { 414 elem.getExtraProperties().remove(name); 415 } 416 else 417 { 418 elem.getExtraProperties().put(name, newLinkedList(value)); 419 } 420 } 421 422 /** 423 * Sets the values for an "extra" property for this schema element. 424 * If a property already exists with the specified name, then it 425 * will be overwritten. If the set of values is {@code null} or 426 * empty, then any existing property with the given name will be 427 * removed. 428 * 429 * @param name The name for the "extra" property. It must not 430 * be {@code null}. 431 * @param values The set of values for the "extra" property. If 432 * it is {@code null} or empty, then any existing 433 * definition will be removed. 434 */ 435 public final void setExtraProperty(String name, 436 List<String> values) { 437 ifNull(name); 438 439 if (values == null || values.isEmpty()) 440 { 441 extraProperties.remove(name); 442 } 443 else 444 { 445 LinkedList<String> valuesCopy = new LinkedList<>(values); 446 extraProperties.put(name, valuesCopy); 447 } 448 } 449 450 /** 451 * Indicates whether the provided object is equal to this attribute 452 * type. The object will be considered equal if it is an attribute 453 * type with the same OID as the current type. 454 * 455 * @param o 456 * The object for which to make the determination. 457 * @return <code>true</code> if the provided object is equal to 458 * this schema definition, or <code>false</code> if not. 459 */ 460 @Override 461 public final boolean equals(Object o) { 462 if (this == o) { 463 return true; 464 } 465 466 if (o instanceof CommonSchemaElements) { 467 CommonSchemaElements other = (CommonSchemaElements) o; 468 return getNameOrOID().equals(other.getNameOrOID()); 469 } 470 471 return false; 472 } 473 474 /** 475 * Retrieves the hash code for this schema definition. It will be 476 * based on the sum of the bytes of the OID. 477 * 478 * @return The hash code for this schema definition. 479 */ 480 @Override 481 public final int hashCode() { 482 return hashCode; 483 } 484 485 /** 486 * Retrieves the definition string used to create this attribute 487 * type and including the X-SCHEMA-FILE extension. 488 * 489 * @param elem The element where to get definition from 490 * @return The definition string used to create this attribute 491 * type including the X-SCHEMA-FILE extension. 492 */ 493 public static String getDefinitionWithFileName(SchemaFileElement elem) 494 { 495 final String schemaFile = getSchemaFile(elem); 496 final String definition = elem.toString(); 497 if (schemaFile != null) 498 { 499 int pos = definition.lastIndexOf(')'); 500 return definition.substring(0, pos).trim() + " " 501 + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )"; 502 } 503 return definition; 504 } 505}