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    }