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.LinkedHashMap; 032import java.util.LinkedList; 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 this.primaryName = primaryName; 149 } else if (names != null && !names.isEmpty()) { 150 this.primaryName = names.iterator().next(); 151 } else { 152 this.primaryName = null; 153 } 154 this.lowerName = this.primaryName != null ? toLowerCase(this.primaryName) : oid; 155 156 // OPENDJ-1645: oid changes during server bootstrap, so prefer using lowername if available 157 hashCode = this.lowerName.hashCode(); 158 159 // Construct the normalized attribute name mapping. 160 if (names != null) { 161 this.names = new LinkedHashMap<>(names.size()); 162 163 // Make sure the primary name is first (never null). 164 this.names.put(lowerName, this.primaryName); 165 166 // Add the remaining names in the order specified. 167 for (String name : names) { 168 this.names.put(toLowerCase(name), name); 169 } 170 } else if (this.primaryName != null) { 171 this.names = Collections.singletonMap(lowerName, this.primaryName); 172 } else { 173 this.names = Collections.emptyMap(); 174 } 175 176 // FIXME: should really be a deep-copy. 177 if (extraProperties != null) { 178 this.extraProperties = new LinkedHashMap<>(extraProperties); 179 } else { 180 this.extraProperties = Collections.emptyMap(); 181 } 182 } 183 184 /** 185 * Check if the extra schema properties contain safe filenames. 186 * 187 * @param extraProperties 188 * The schema properties to check. 189 * 190 * @throws DirectoryException 191 * If a provided value was unsafe. 192 */ 193 public static void checkSafeProperties(Map <String,List<String>> 194 extraProperties) 195 throws DirectoryException 196 { 197 // Check that X-SCHEMA-FILE doesn't contain unsafe characters 198 List<String> filenames = extraProperties.get(SCHEMA_PROPERTY_FILENAME); 199 if (filenames != null && !filenames.isEmpty()) { 200 String filename = filenames.get(0); 201 if (filename.indexOf('/') != -1 || filename.indexOf('\\') != -1) 202 { 203 LocalizableMessage message = ERR_ATTR_SYNTAX_ILLEGAL_X_SCHEMA_FILE.get(filename); 204 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 205 message); 206 } 207 } 208 } 209 210 /** 211 * Retrieves the primary name for this schema definition. 212 * 213 * @return The primary name for this schema definition, or 214 * <code>null</code> if there is no primary name. 215 */ 216 public final String getPrimaryName() { 217 return primaryName; 218 } 219 220 /** 221 * Retrieves an iterable over the set of normalized names that may 222 * be used to reference this schema definition. The normalized form 223 * of an attribute name is defined as the user-defined name 224 * converted to lower-case. 225 * 226 * @return Returns an iterable over the set of normalized names that 227 * may be used to reference this schema definition. 228 */ 229 public final Set<String> getNormalizedNames() { 230 return names.keySet(); 231 } 232 233 /** 234 * Retrieves an iterable over the set of user-defined names that may 235 * be used to reference this schema definition. 236 * 237 * @return Returns an iterable over the set of user-defined names 238 * that may be used to reference this schema definition. 239 */ 240 public final Iterable<String> getUserDefinedNames() { 241 return names.values(); 242 } 243 244 /** 245 * Indicates whether this schema definition has the specified name. 246 * 247 * @param lowerName 248 * The lowercase name for which to make the determination. 249 * @return <code>true</code> if the specified name is assigned to 250 * this schema definition, or <code>false</code> if not. 251 */ 252 public final boolean hasName(String lowerName) { 253 return names.containsKey(lowerName); 254 } 255 256 /** 257 * Retrieves the OID for this schema definition. 258 * 259 * @return The OID for this schema definition. 260 */ 261 public final String getOID() { 262 return oid; 263 } 264 265 /** 266 * Retrieves the name or OID for this schema definition. If it has 267 * one or more names, then the primary name will be returned. If it 268 * does not have any names, then the OID will be returned. 269 * 270 * @return The name or OID for this schema definition. 271 */ 272 public final String getNameOrOID() { 273 if (primaryName != null) { 274 return primaryName; 275 } 276 // Guaranteed not to be null. 277 return oid; 278 } 279 280 /** 281 * Retrieves the normalized primary name or OID for this schema 282 * definition. If it does not have any names, then the OID will be 283 * returned. 284 * 285 * @return The name or OID for this schema definition. 286 */ 287 public final String getNormalizedPrimaryNameOrOID() { 288 return lowerName; 289 } 290 291 /** 292 * Indicates whether this schema definition has the specified name 293 * or OID. 294 * 295 * @param lowerValue 296 * The lowercase value for which to make the determination. 297 * @return <code>true</code> if the provided value matches the OID 298 * or one of the names assigned to this schema definition, 299 * or <code>false</code> if not. 300 */ 301 public final boolean hasNameOrOID(String lowerValue) { 302 return names.containsKey(lowerValue) || oid.equals(lowerValue); 303 } 304 305 /** 306 * Retrieves the name of the schema file that contains the 307 * definition for this schema definition. 308 * 309 * @param elem The element where to get the schema file from 310 * @return The name of the schema file that contains the definition 311 * for this schema definition, or <code>null</code> if it 312 * is not known or if it is not stored in any schema file. 313 */ 314 public static String getSchemaFile(SchemaFileElement elem) 315 { 316 return getSingleValueProperty(elem, SCHEMA_PROPERTY_FILENAME); 317 } 318 319 /** 320 * Retrieves the name of a single value property for this schema element. 321 * 322 * @param elem The element where to get the single value property from 323 * @param propertyName The name of the property to get 324 * @return The single value for this property, or <code>null</code> if it 325 * is this property is not set. 326 */ 327 public static String getSingleValueProperty(SchemaFileElement elem, 328 String propertyName) 329 { 330 List<String> values = elem.getExtraProperties().get(propertyName); 331 if (values != null && !values.isEmpty()) { 332 return values.get(0); 333 } 334 return null; 335 } 336 337 /** 338 * Specifies the name of the schema file that contains the 339 * definition for this schema element. If a schema file is already 340 * defined in the set of extra properties, then it will be 341 * overwritten. If the provided schema file value is {@code null}, 342 * then any existing schema file definition will be removed. 343 * 344 * @param elem The element where to set the schema file 345 * @param schemaFile The name of the schema file that contains the 346 * definition for this schema element. 347 */ 348 public static void setSchemaFile(SchemaFileElement elem, String schemaFile) 349 { 350 setExtraProperty(elem, SCHEMA_PROPERTY_FILENAME, schemaFile); 351 } 352 353 /** 354 * Retrieves the description for this schema definition. 355 * 356 * @return The description for this schema definition, or 357 * <code>null</code> if there is no description. 358 */ 359 public final String getDescription() { 360 return description; 361 } 362 363 /** 364 * Indicates whether this schema definition is declared "obsolete". 365 * 366 * @return <code>true</code> if this schema definition is declared 367 * "obsolete", or <code>false</code> if not. 368 */ 369 public final boolean isObsolete() { 370 return isObsolete; 371 } 372 373 @Override 374 public final Map<String, List<String>> getExtraProperties() 375 { 376 return extraProperties; 377 } 378 379 /** 380 * Sets the value for an "extra" property for this schema element. 381 * If a property already exists with the specified name, then it 382 * will be overwritten. If the value is {@code null}, then any 383 * existing property with the given name will be removed. 384 * 385 * @param elem The element where to set the extra property 386 * @param name The name for the "extra" property. It must not be 387 * {@code null}. 388 * @param value The value for the "extra" property. If it is 389 * {@code null}, then any existing definition will be removed. 390 */ 391 public static void setExtraProperty(SchemaFileElement elem, 392 String name, String value) 393 { 394 ifNull(name); 395 396 if (value == null) 397 { 398 elem.getExtraProperties().remove(name); 399 } 400 else 401 { 402 elem.getExtraProperties().put(name, newLinkedList(value)); 403 } 404 } 405 406 /** 407 * Sets the values for an "extra" property for this schema element. 408 * If a property already exists with the specified name, then it 409 * will be overwritten. If the set of values is {@code null} or 410 * empty, then any existing property with the given name will be 411 * removed. 412 * 413 * @param name The name for the "extra" property. It must not 414 * be {@code null}. 415 * @param values The set of values for the "extra" property. If 416 * it is {@code null} or empty, then any existing 417 * definition will be removed. 418 */ 419 public final void setExtraProperty(String name, 420 List<String> values) { 421 ifNull(name); 422 423 if (values == null || values.isEmpty()) 424 { 425 extraProperties.remove(name); 426 } 427 else 428 { 429 LinkedList<String> valuesCopy = new LinkedList<>(values); 430 extraProperties.put(name, valuesCopy); 431 } 432 } 433 434 /** 435 * Indicates whether the provided object is equal to this attribute 436 * type. The object will be considered equal if it is an attribute 437 * type with the same OID as the current type. 438 * 439 * @param o 440 * The object for which to make the determination. 441 * @return <code>true</code> if the provided object is equal to 442 * this schema definition, or <code>false</code> if not. 443 */ 444 @Override 445 public final boolean equals(Object o) { 446 if (this == o) { 447 return true; 448 } 449 450 if (o instanceof CommonSchemaElements) { 451 CommonSchemaElements other = (CommonSchemaElements) o; 452 return lowerName.equals(other.lowerName); 453 } 454 return false; 455 } 456 457 /** 458 * Retrieves the hash code for this schema definition. It will be 459 * based on the sum of the bytes of the OID. 460 * 461 * @return The hash code for this schema definition. 462 */ 463 @Override 464 public final int hashCode() { 465 return hashCode; 466 } 467 468 /** 469 * Retrieves the definition string used to create this attribute 470 * type and including the X-SCHEMA-FILE extension. 471 * 472 * @param elem The element where to get definition from 473 * @return The definition string used to create this attribute 474 * type including the X-SCHEMA-FILE extension. 475 */ 476 public static String getDefinitionWithFileName(SchemaFileElement elem) 477 { 478 final String schemaFile = getSchemaFile(elem); 479 final String definition = elem.toString(); 480 if (schemaFile != null) 481 { 482 int pos = definition.lastIndexOf(')'); 483 return definition.substring(0, pos).trim() + " " 484 + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )"; 485 } 486 return definition; 487 } 488}