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
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.types;
028
029 import static org.opends.server.util.ServerConstants.*;
030 import static org.opends.server.util.StaticUtils.toLowerCase;
031 import static org.opends.server.util.Validator.*;
032
033 import java.util.Collection;
034 import java.util.Collections;
035 import java.util.Iterator;
036 import java.util.LinkedList;
037 import java.util.LinkedHashMap;
038 import java.util.List;
039 import java.util.Map;
040
041
042
043 /**
044 * An abstract base class for LDAP schema definitions which contain an
045 * OID, optional names, description, an obsolete flag, and an optional
046 * set of extra properties.
047 * <p>
048 * This class defines common properties and behaviour of the various
049 * types of schema definitions (e.g. object class definitions, and
050 * attribute type definitions).
051 * <p>
052 * Any methods which accesses the set of names associated with this
053 * definition, will retrieve the primary name as the first name,
054 * regardless of whether or not it was contained in the original set
055 * of <code>names</code> passed to the constructor.
056 * <p>
057 * Where ordered sets of names, or extra properties are provided, the
058 * ordering will be preserved when the associated fields are accessed
059 * via their getters or via the {@link #toString()} methods.
060 * <p>
061 * Note that these schema elements are not completely immutable, as
062 * the set of extra properties for the schema element may be altered
063 * after the element is created. Among other things, this allows the
064 * associated schema file to be edited so that an element created over
065 * protocol may be associated with a particular schema file.
066 */
067 @org.opends.server.types.PublicAPI(
068 stability=org.opends.server.types.StabilityLevel.VOLATILE,
069 mayInstantiate=false,
070 mayExtend=false,
071 mayInvoke=true)
072 public abstract class CommonSchemaElements {
073
074 // Indicates whether this definition is declared "obsolete".
075 private final boolean isObsolete;
076
077 // The hash code for this definition.
078 private final int hashCode;
079
080 // The set of additional name-value pairs associated with this
081 // definition.
082 private final Map<String, List<String>> extraProperties;
083
084 // The set of names for this definition, in a mapping between
085 // the all-lowercase form and the user-defined form.
086 private final Map<String, String> names;
087
088 // The description for this definition.
089 private final String description;
090
091 // The OID that may be used to reference this definition.
092 private final String oid;
093
094 // The primary name to use for this definition.
095 private final String primaryName;
096
097 // The lower case name for this definition.
098 private final String lowerName;
099
100
101
102 /**
103 * Creates a new definition with the provided information.
104 * <p>
105 * If no <code>primaryName</code> is specified, but a set of
106 * <code>names</code> is specified, then the first name retrieved
107 * from the set of <code>names</code> will be used as the primary
108 * name.
109 *
110 * @param primaryName
111 * The primary name for this definition, or
112 * <code>null</code> if there is no primary name.
113 * @param names
114 * The full set of names for this definition, or
115 * <code>null</code> if there are no names.
116 * @param oid
117 * The OID for this definition (must not be
118 * <code>null</code>).
119 * @param description
120 * The description for the definition, or <code>null</code>
121 * if there is no description.
122 * @param isObsolete
123 * Indicates whether this definition is declared
124 * "obsolete".
125 * @param extraProperties
126 * A set of extra properties for this definition, or
127 * <code>null</code> if there are no extra properties.
128 * @throws NullPointerException
129 * If the provided OID was <code>null</code>.
130 */
131 protected CommonSchemaElements(String primaryName,
132 Collection<String> names, String oid, String description,
133 boolean isObsolete, Map<String, List<String>> extraProperties)
134 throws NullPointerException {
135
136
137 // Make sure mandatory parameters are specified.
138 if (oid == null) {
139 throw new NullPointerException(
140 "No oid specified in constructor");
141 }
142
143 this.oid = oid;
144 this.description = description;
145 this.isObsolete = isObsolete;
146
147 hashCode = oid.hashCode();
148
149 // Make sure we have a primary name if possible.
150 if (primaryName == null) {
151 if (names != null && !names.isEmpty()) {
152 this.primaryName = names.iterator().next();
153 } else {
154 this.primaryName = null;
155 }
156 } else {
157 this.primaryName = primaryName;
158 }
159 this.lowerName = toLowerCase(primaryName);
160
161 // Construct the normalized attribute name mapping.
162 if (names != null) {
163 this.names = new LinkedHashMap<String, String>(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,
174 this.primaryName);
175 } else {
176 this.names = Collections.emptyMap();
177 }
178
179 // FIXME: should really be a deep-copy.
180 if (extraProperties != null) {
181 this.extraProperties = new LinkedHashMap<String, List<String>>(
182 extraProperties);
183 } else {
184 this.extraProperties = Collections.emptyMap();
185 }
186 }
187
188
189
190 /**
191 * Retrieves the primary name for this schema definition.
192 *
193 * @return The primary name for this schema definition, or
194 * <code>null</code> if there is no primary name.
195 */
196 public final String getPrimaryName() {
197
198 return primaryName;
199 }
200
201
202
203 /**
204 * Retrieve the normalized primary name for this schema definition.
205 *
206 * @return Returns the normalized primary name for this attribute
207 * type, or <code>null</code> if there is no primary name.
208 */
209 public final String getNormalizedPrimaryName() {
210
211 return lowerName;
212 }
213
214
215
216 /**
217 * Retrieves an iterable over the set of normalized names that may
218 * be used to reference this schema definition. The normalized form
219 * of an attribute name is defined as the user-defined name
220 * converted to lower-case.
221 *
222 * @return Returns an iterable over the set of normalized names that
223 * may be used to reference this schema definition.
224 */
225 public final Iterable<String> getNormalizedNames() {
226
227 return names.keySet();
228 }
229
230
231
232 /**
233 * Retrieves an iterable over the set of user-defined names that may
234 * be used to reference this schema definition.
235 *
236 * @return Returns an iterable over the set of user-defined names
237 * that may be used to reference this schema definition.
238 */
239 public final Iterable<String> getUserDefinedNames() {
240
241 return names.values();
242 }
243
244
245
246 /**
247 * Indicates whether this schema definition has the specified name.
248 *
249 * @param lowerName
250 * The lowercase name for which to make the determination.
251 * @return <code>true</code> if the specified name is assigned to
252 * this schema definition, or <code>false</code> if not.
253 */
254 public final boolean hasName(String lowerName) {
255
256 return names.containsKey(lowerName);
257 }
258
259
260
261 /**
262 * Retrieves the OID for this schema definition.
263 *
264 * @return The OID for this schema definition.
265 */
266 public final String getOID() {
267
268 return oid;
269 }
270
271
272
273 /**
274 * Retrieves the name or OID for this schema definition. If it has
275 * one or more names, then the primary name will be returned. If it
276 * does not have any names, then the OID will be returned.
277 *
278 * @return The name or OID for this schema definition.
279 */
280 public final String getNameOrOID() {
281
282 if (primaryName != null) {
283 return primaryName;
284 } else {
285 // Guaranteed not to be null.
286 return oid;
287 }
288 }
289
290
291
292 /**
293 * Indicates whether this schema definition has the specified name
294 * or OID.
295 *
296 * @param lowerValue
297 * The lowercase value for which to make the determination.
298 * @return <code>true</code> if the provided value matches the OID
299 * or one of the names assigned to this schema definition,
300 * or <code>false</code> if not.
301 */
302 public final boolean hasNameOrOID(String lowerValue) {
303
304 if (names.containsKey(lowerValue)) {
305 return true;
306 }
307
308 return oid.equals(lowerValue);
309 }
310
311
312
313 /**
314 * Retrieves the name of the schema file that contains the
315 * definition for this schema definition.
316 *
317 * @return The name of the schema file that contains the definition
318 * for this schema definition, or <code>null</code> if it
319 * is not known or if it is not stored in any schema file.
320 */
321 public final String getSchemaFile() {
322
323 List<String> values = extraProperties
324 .get(SCHEMA_PROPERTY_FILENAME);
325 if (values != null && !values.isEmpty()) {
326 return values.get(0);
327 }
328
329 return null;
330 }
331
332
333
334 /**
335 * Specifies the name of the schema file that contains the
336 * definition for this schema element. If a schema file is already
337 * defined in the set of extra properties, then it will be
338 * overwritten. If the provided schema file value is {@code null},
339 * then any existing schema file definition will be removed.
340 *
341 * @param schemaFile The name of the schema file that contains the
342 * definition for this schema element.
343 */
344 public final void setSchemaFile(String schemaFile) {
345
346 setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
347 }
348
349
350
351 /**
352 * Retrieves the description for this schema definition.
353 *
354 * @return The description for this schema definition, or
355 * <code>null</code> if there is no description.
356 */
357 public final String getDescription() {
358
359 return description;
360 }
361
362
363
364 /**
365 * Indicates whether this schema definition is declared "obsolete".
366 *
367 * @return <code>true</code> if this schema definition is declared
368 * "obsolete", or <code>false</code> if not.
369 */
370 public final boolean isObsolete() {
371
372 return isObsolete;
373 }
374
375
376
377 /**
378 * Retrieves an iterable over the names of "extra" properties
379 * associated with this schema definition.
380 *
381 * @return Returns an iterable over the names of "extra" properties
382 * associated with this schema definition.
383 */
384 public final Iterable<String> getExtraPropertyNames() {
385
386 return extraProperties.keySet();
387 }
388
389
390
391 /**
392 * Retrieves an iterable over the value(s) of the specified "extra"
393 * property for this schema definition.
394 *
395 * @param name
396 * The name of the "extra" property for which to retrieve
397 * the value(s).
398 * @return Returns an iterable over the value(s) of the specified
399 * "extra" property for this schema definition, or
400 * <code>null</code> if no such property is defined.
401 */
402 public final Iterable<String> getExtraProperty(String name) {
403
404 return extraProperties.get(name);
405 }
406
407
408
409 /**
410 * Sets the value for an "extra" property for this schema element.
411 * If a property already exists with the specified name, then it
412 * will be overwritten. If the value is {@code null}, then any
413 * existing property with the given name will be removed.
414 *
415 * @param name The name for the "extra" property. It must not be
416 * {@code null}.
417 * @param value The value for the "extra" property. If it is
418 * {@code null}, then any existing definition will be
419 * removed.
420 */
421 public final void setExtraProperty(String name, String value) {
422
423 ensureNotNull(name);
424
425 if (value == null)
426 {
427 extraProperties.remove(name);
428 }
429 else
430 {
431 LinkedList<String> values = new LinkedList<String>();
432 values.add(value);
433
434 extraProperties.put(name, values);
435 }
436 }
437
438
439
440 /**
441 * Sets the values for an "extra" property for this schema element.
442 * If a property already exists with the specified name, then it
443 * will be overwritten. If the set of values is {@code null} or
444 * empty, then any existing property with the given name will be
445 * removed.
446 *
447 * @param name The name for the "extra" property. It must not
448 * be {@code null}.
449 * @param values The set of values for the "extra" property. If
450 * it is {@code null} or empty, then any existing
451 * definition will be removed.
452 */
453 public final void setExtraProperty(String name,
454 List<String> values) {
455
456 ensureNotNull(name);
457
458 if ((values == null) || values.isEmpty())
459 {
460 extraProperties.remove(name);
461 }
462 else
463 {
464 LinkedList<String> valuesCopy = new LinkedList<String>(values);
465 extraProperties.put(name, valuesCopy);
466 }
467 }
468
469
470
471 /**
472 * Indicates whether the provided object is equal to this attribute
473 * type. The object will be considered equal if it is an attribute
474 * type with the same OID as the current type.
475 *
476 * @param o
477 * The object for which to make the determination.
478 * @return <code>true</code> if the provided object is equal to
479 * this schema definition, or <code>false</code> if not.
480 */
481 public final boolean equals(Object o) {
482
483 if (this == o) {
484 return true;
485 }
486
487 if (o instanceof CommonSchemaElements) {
488 CommonSchemaElements other = (CommonSchemaElements) o;
489 return oid.equals(other.oid);
490 }
491
492 return false;
493 }
494
495
496
497 /**
498 * Retrieves the hash code for this schema definition. It will be
499 * based on the sum of the bytes of the OID.
500 *
501 * @return The hash code for this schema definition.
502 */
503 public final int hashCode() {
504
505 return hashCode;
506 }
507
508
509
510 /**
511 * Retrieves the string representation of this schema definition in
512 * the form specified in RFC 2252.
513 *
514 * @return The string representation of this schema definition in
515 * the form specified in RFC 2252.
516 */
517 public final String toString() {
518
519 StringBuilder buffer = new StringBuilder();
520 toString(buffer, true);
521 return buffer.toString();
522 }
523
524
525
526 /**
527 * Appends a string representation of this schema definition in the
528 * form specified in RFC 2252 to the provided buffer.
529 *
530 * @param buffer
531 * The buffer to which the information should be appended.
532 * @param includeFileElement
533 * Indicates whether to include an "extra" property that
534 * specifies the path to the schema file from which this
535 * schema definition was loaded.
536 */
537 public final void toString(StringBuilder buffer,
538 boolean includeFileElement) {
539
540 buffer.append("( ");
541 buffer.append(oid);
542
543 if (!names.isEmpty()) {
544 Iterator<String> iterator = names.values().iterator();
545
546 String firstName = iterator.next();
547 if (iterator.hasNext()) {
548 buffer.append(" NAME ( '");
549 buffer.append(firstName);
550
551 while (iterator.hasNext()) {
552 buffer.append("' '");
553 buffer.append(iterator.next());
554 }
555
556 buffer.append("' )");
557 } else {
558 buffer.append(" NAME '");
559 buffer.append(firstName);
560 buffer.append("'");
561 }
562 }
563
564 if ((description != null) && (description.length() > 0)) {
565 buffer.append(" DESC '");
566 buffer.append(description);
567 buffer.append("'");
568 }
569
570 if (isObsolete) {
571 buffer.append(" OBSOLETE");
572 }
573
574 // Delegate remaining string output to sub-class.
575 toStringContent(buffer);
576
577 if (!extraProperties.isEmpty()) {
578 for (Map.Entry<String, List<String>> e : extraProperties
579 .entrySet()) {
580
581 String property = e.getKey();
582 if (!includeFileElement
583 && property.equals(SCHEMA_PROPERTY_FILENAME)) {
584 // Don't include the schema file if it was not requested.
585 continue;
586 }
587
588 List<String> valueList = e.getValue();
589
590 buffer.append(" ");
591 buffer.append(property);
592
593 if (valueList.size() == 1) {
594 buffer.append(" '");
595 buffer.append(valueList.get(0));
596 buffer.append("'");
597 } else {
598 buffer.append(" ( ");
599
600 for (String value : valueList) {
601 buffer.append("'");
602 buffer.append(value);
603 buffer.append("' ");
604 }
605
606 buffer.append(")");
607 }
608 }
609 }
610
611 buffer.append(" )");
612 }
613
614
615
616 /**
617 * Appends a string representation of this schema definition's
618 * non-generic properties to the provided buffer.
619 *
620 * @param buffer
621 * The buffer to which the information should be appended.
622 */
623 protected abstract void toStringContent(StringBuilder buffer);
624 }