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