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    
030    
031    import java.util.Iterator;
032    import java.util.LinkedHashMap;
033    import java.util.LinkedHashSet;
034    import java.util.LinkedList;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    
039    import org.opends.server.schema.NameFormSyntax;
040    
041    import static org.opends.server.loggers.debug.DebugLogger.*;
042    import org.opends.server.loggers.debug.DebugTracer;
043    import static org.opends.server.util.ServerConstants.*;
044    import static org.opends.server.util.StaticUtils.*;
045    import static org.opends.server.util.Validator.*;
046    
047    
048    
049    /**
050     * This class defines a data structure for storing and interacting
051     * with a name form, which defines the attribute type(s) that must
052     * and/or may be used in the RDN of an entry with a given structural
053     * objectclass.
054     */
055    @org.opends.server.types.PublicAPI(
056         stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
057         mayInstantiate=false,
058         mayExtend=false,
059         mayInvoke=true)
060    public final class NameForm
061           implements SchemaFileElement
062    {
063      /**
064       * The tracer object for the debug logger.
065       */
066      private static final DebugTracer TRACER = getTracer();
067    
068      // Indicates whether this name form is declared "obsolete".
069      private final boolean isObsolete;
070    
071      // The set of additional name-value pairs associated with this name
072      // form definition.
073      private final Map<String,List<String>> extraProperties;
074    
075      // The mapping between the lowercase names and the user-provided
076      // names for this name form.
077      private final Map<String,String> names;
078    
079      // The reference to the structural objectclass for this name form.
080      private final ObjectClass structuralClass;
081    
082      // The set of optional attribute types for this name form.
083      private final Set<AttributeType> optionalAttributes;
084    
085      // The set of required attribute types for this name form.
086      private final Set<AttributeType> requiredAttributes;
087    
088      // The definition string used to create this name form.
089      private final String definition;
090    
091      // The description for this name form.
092      private final String description;
093    
094      // The OID for this name form.
095      private final String oid;
096    
097    
098    
099      /**
100       * Creates a new name form definition with the provided information.
101       *
102       * @param  definition          The definition string used to create
103       *                             this name form.  It must not be
104       *                             {@code null}.
105       * @param  names               The set of names that may be used to
106       *                             reference this name form.
107       * @param  oid                 The OID for this name form.  It must
108       *                             not be {@code null}.
109       * @param  description         The description for this name form.
110       * @param  isObsolete          Indicates whether this name form is
111       *                             declared "obsolete".
112       * @param  structuralClass     The structural objectclass with which
113       *                             this name form is associated.  It
114       *                             must not be {@code null}.
115       * @param  requiredAttributes  The set of required attribute types
116       *                             for this name form.
117       * @param  optionalAttributes  The set of optional attribute types
118       *                             for this name form.
119       * @param  extraProperties     A set of extra properties for this
120       *                             name form.
121       */
122      public NameForm(String definition, Map<String,String> names,
123                      String oid, String description, boolean isObsolete,
124                      ObjectClass structuralClass,
125                      Set<AttributeType> requiredAttributes,
126                      Set<AttributeType> optionalAttributes,
127                      Map<String,List<String>> extraProperties)
128      {
129        ensureNotNull(definition, oid, structuralClass);
130    
131        this.oid             = oid;
132        this.description     = description;
133        this.isObsolete      = isObsolete;
134        this.structuralClass = structuralClass;
135    
136        int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
137        if (schemaFilePos > 0)
138        {
139          String defStr;
140          try
141          {
142            int firstQuotePos = definition.indexOf('\'', schemaFilePos);
143            int secondQuotePos = definition.indexOf('\'',
144                                                    firstQuotePos+1);
145    
146            defStr = definition.substring(0, schemaFilePos).trim() + " " +
147                     definition.substring(secondQuotePos+1).trim();
148          }
149          catch (Exception e)
150          {
151            if (debugEnabled())
152            {
153              TRACER.debugCaught(DebugLogLevel.ERROR, e);
154            }
155    
156            defStr = definition;
157          }
158    
159          this.definition = defStr;
160        }
161        else
162        {
163          this.definition = definition;
164        }
165    
166        if ((names == null) || names.isEmpty())
167        {
168          this.names = new LinkedHashMap<String,String>(0);
169        }
170        else
171        {
172          this.names = new LinkedHashMap<String,String>(names);
173        }
174    
175        if ((requiredAttributes == null) || requiredAttributes.isEmpty())
176        {
177          this.requiredAttributes = new LinkedHashSet<AttributeType>(0);
178        }
179        else
180        {
181          this.requiredAttributes =
182               new LinkedHashSet<AttributeType>(requiredAttributes);
183        }
184    
185        if ((optionalAttributes == null) || optionalAttributes.isEmpty())
186        {
187          this.optionalAttributes = new LinkedHashSet<AttributeType>(0);
188        }
189        else
190        {
191          this.optionalAttributes =
192               new LinkedHashSet<AttributeType>(optionalAttributes);
193        }
194    
195        if ((extraProperties == null) || extraProperties.isEmpty())
196        {
197          this.extraProperties =
198               new LinkedHashMap<String,List<String>>(0);
199        }
200        else
201        {
202          this.extraProperties =
203               new LinkedHashMap<String,List<String>>(extraProperties);
204        }
205      }
206    
207    
208    
209      /**
210       * Retrieves the definition string used to create this name form.
211       *
212       * @return  The definition string used to create this name form.
213       */
214      public String getDefinition()
215      {
216        return definition;
217      }
218    
219    
220    
221      /**
222       * Creates a new instance of this name form based on the definition
223       * string.  It will also preserve other state information associated
224       * with this name form that is not included in the definition string
225       * (e.g., the name of the schema file with which it is associated).
226       *
227       * @return  The new instance of this name form based on the
228       *          definition string.
229       *
230       * @throws  DirectoryException  If a problem occurs while attempting
231       *                              to create a new name form instance
232       *                              from the definition string.
233       */
234      public NameForm recreateFromDefinition()
235             throws DirectoryException
236      {
237        ByteString value  = ByteStringFactory.create(definition);
238        Schema     schema = DirectoryConfig.getSchema();
239    
240        NameForm nf = NameFormSyntax.decodeNameForm(value, schema, false);
241        nf.setSchemaFile(getSchemaFile());
242    
243        return nf;
244      }
245    
246    
247    
248      /**
249       * Retrieves the set of names that may be used to reference this
250       * name form.  The returned object will be a mapping between each
251       * name in all lowercase characters and that name in a user-defined
252       * form (which may include mixed capitalization).
253       *
254       * @return  The set of names that may be used to reference this
255       *          name form.
256       */
257      public Map<String,String> getNames()
258      {
259        return names;
260      }
261    
262    
263    
264      /**
265       * Indicates whether the provided lowercase name may be used to
266       * reference this name form.
267       *
268       * @param  lowerName  The name for which to make the determination,
269       *                    in all lowercase characters.
270       *
271       * @return  {@code true} if the provided lowercase name may be used
272       *          to reference this name form, or {@code false} if not.
273       */
274      public boolean hasName(String lowerName)
275      {
276        return names.containsKey(lowerName);
277      }
278    
279    
280    
281      /**
282       * Retrieves the OID for this name form.
283       *
284       * @return  The OID for this name form.
285       */
286      public String getOID()
287      {
288        return oid;
289      }
290    
291    
292    
293      /**
294       * Retrieves the name or OID that should be used to reference this
295       * name form.  If at least one name is defined, then the first will
296       * be returned.  Otherwise, the OID will be returned.
297       *
298       * @return  The name or OID that should be used to reference this
299       *          name form.
300       */
301      public String getNameOrOID()
302      {
303        if (names.isEmpty())
304        {
305          return oid;
306        }
307        else
308        {
309          return names.values().iterator().next();
310        }
311      }
312    
313    
314    
315      /**
316       * Indicates whether the provided lowercase value is equal to the
317       * OID or any of the names that may be used to reference this name
318       * form.
319       *
320       * @param  lowerValue  The value, in all lowercase characters, that
321       *                     may be used to make the determination.
322       *
323       * @return  {@code true} if the provided lowercase value is one of
324       *          the names or the OID of this name form, or {@code false}
325       *          if it is not.
326       */
327      public boolean hasNameOrOID(String lowerValue)
328      {
329        if (names.containsKey(lowerValue))
330        {
331          return true;
332        }
333    
334        return lowerValue.equals(oid);
335      }
336    
337    
338    
339      /**
340       * Retrieves the path to the schema file that contains the
341       * definition for this name form.
342       *
343       * @return  The path to the schema file that contains the definition
344       *          for this name form, or {@code null} if it is not known
345       *          or if it is not stored in any schema file.
346       */
347      public String getSchemaFile()
348      {
349        List<String> values =
350             extraProperties.get(SCHEMA_PROPERTY_FILENAME);
351        if ((values == null) || values.isEmpty())
352        {
353          return null;
354        }
355    
356        return values.get(0);
357      }
358    
359    
360    
361      /**
362       * Specifies the path to the schema file that contains the
363       * definition for this name form.
364       *
365       * @param  schemaFile  The path to the schema file that contains the
366       *                     definition for this name form.
367       */
368      public void setSchemaFile(String schemaFile)
369      {
370        setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
371      }
372    
373    
374    
375      /**
376       * Retrieves the description for this name form.
377       *
378       * @return  The description for this name form, or {@code true} if
379       *          there is none.
380       */
381      public String getDescription()
382      {
383        return description;
384      }
385    
386    
387    
388      /**
389       * Retrieves the reference to the structural objectclass for this
390       * name form.
391       *
392       * @return  The reference to the structural objectclass for this
393       *          name form.
394       */
395      public ObjectClass getStructuralClass()
396      {
397        return structuralClass;
398      }
399    
400    
401    
402      /**
403       * Retrieves the set of required attributes for this name form.
404       *
405       * @return  The set of required attributes for this name form.
406       */
407      public Set<AttributeType> getRequiredAttributes()
408      {
409        return requiredAttributes;
410      }
411    
412    
413    
414      /**
415       * Indicates whether the provided attribute type is included in the
416       * required attribute list for this name form.
417       *
418       * @param  attributeType  The attribute type for which to make the
419       *                        determination.
420       *
421       * @return  {@code true} if the provided attribute type is required
422       *          by this name form, or {@code false} if not.
423       */
424      public boolean isRequired(AttributeType attributeType)
425      {
426        return requiredAttributes.contains(attributeType);
427      }
428    
429    
430    
431      /**
432       * Retrieves the set of optional attributes for this name form.
433       *
434       * @return  The set of optional attributes for this name form.
435       */
436      public Set<AttributeType> getOptionalAttributes()
437      {
438        return optionalAttributes;
439      }
440    
441    
442    
443      /**
444       * Indicates whether the provided attribute type is included in the
445       * optional attribute list for this name form.
446       *
447       * @param  attributeType  The attribute type for which to make the
448       *                        determination.
449       *
450       * @return  {@code true} if the provided attribute type is optional
451       *          for this name form, or {@code false} if not.
452       */
453      public boolean isOptional(AttributeType attributeType)
454      {
455        return optionalAttributes.contains(attributeType);
456      }
457    
458    
459    
460      /**
461       * Indicates whether the provided attribute type is in the list of
462       * required or optional attributes for this name form.
463       *
464       * @param  attributeType  The attribute type for which to make the
465       *                        determination.
466       *
467       * @return  {@code true} if the provided attribute type is required
468       *          or optional for this name form, or {@code false} if it
469       *          is not.
470       */
471      public boolean isRequiredOrOptional(AttributeType attributeType)
472      {
473        return (requiredAttributes.contains(attributeType) ||
474                optionalAttributes.contains(attributeType));
475      }
476    
477    
478    
479      /**
480       * Indicates whether this name form is declared "obsolete".
481       *
482       * @return  {@code true} if this name form is declared
483       *          "obsolete", or {@code false} if it is not.
484       */
485      public boolean isObsolete()
486      {
487        return isObsolete;
488      }
489    
490    
491    
492      /**
493       * Retrieves a mapping between the names of any extra non-standard
494       * properties that may be associated with this name form and the
495       * value for that property.
496       *
497       * @return  A mapping between the names of any extra non-standard
498       *          properties that may be associated with this name form
499       *          and the value for that property.
500       */
501      public Map<String,List<String>> getExtraProperties()
502      {
503        return extraProperties;
504      }
505    
506    
507    
508      /**
509       * Retrieves the value of the specified "extra" property for this
510       * name form.
511       *
512       * @param  propertyName  The name of the "extra" property for which
513       *                       to retrieve the value.
514       *
515       * @return  The value of the specified "extra" property for this
516       *          name form, or {@code null} if no such property is
517       *          defined.
518       */
519      public List<String> getExtraProperty(String propertyName)
520      {
521        return extraProperties.get(propertyName);
522      }
523    
524    
525    
526      /**
527       * Specifies the provided "extra" property for this name form.
528       *
529       * @param  name   The name for the "extra" property.  It must not be
530       *                {@code null}.
531       * @param  value  The value for the "extra" property, or
532       *                {@code null} if the property is to be removed.
533       */
534      public void setExtraProperty(String name, String value)
535      {
536        ensureNotNull(name);
537    
538        if (value == null)
539        {
540          extraProperties.remove(name);
541        }
542        else
543        {
544          LinkedList<String> values = new LinkedList<String>();
545          values.add(value);
546    
547          extraProperties.put(name, values);
548        }
549      }
550    
551    
552    
553      /**
554       * Specifies the provided "extra" property for this name form.
555       *
556       * @param  name    The name for the "extra" property.  It must not
557       *                 be {@code null}.
558       * @param  values  The set of value for the "extra" property, or
559       *                 {@code null} if the property is to be removed.
560       */
561      public void setExtraProperty(String name, List<String> values)
562      {
563        ensureNotNull(name);
564    
565        if ((values == null) || values.isEmpty())
566        {
567          extraProperties.remove(name);
568        }
569        else
570        {
571          LinkedList<String> valuesCopy = new LinkedList<String>(values);
572          extraProperties.put(name, valuesCopy);
573        }
574      }
575    
576    
577    
578      /**
579       * Indicates whether the provided object is equal to this name form.
580       * The object will be considered equal if it is a name form with the
581       * same OID as the current name form.
582       *
583       * @param  o  The object for which to make the determination.
584       *
585       * @return  {@code true} if the provided object is equal to this
586       *          name form, or {@code true} if not.
587       */
588      public boolean equals(Object o)
589      {
590        if (this == o)
591        {
592          return true;
593        }
594    
595        if ((o == null) || (! (o instanceof NameForm)))
596        {
597          return false;
598        }
599    
600        return oid.equals(((NameForm) o).oid);
601      }
602    
603    
604    
605      /**
606       * Retrieves the hash code for this name form.  It will be based on
607       * the sum of the bytes of the OID.
608       *
609       * @return  The hash code for this name form.
610       */
611      public int hashCode()
612      {
613        int oidLength = oid.length();
614        int hashCode  = 0;
615        for (int i=0; i < oidLength; i++)
616        {
617          hashCode += oid.charAt(i);
618        }
619    
620        return hashCode;
621      }
622    
623    
624    
625      /**
626       * Retrieves the string representation of this name form in the form
627       * specified in RFC 2252.
628       *
629       * @return  The string representation of this name form in the form
630       *          specified in RFC 2252.
631       */
632      public String toString()
633      {
634        StringBuilder buffer = new StringBuilder();
635        toString(buffer, true);
636        return buffer.toString();
637      }
638    
639    
640    
641      /**
642       * Appends a string representation of this name form in the form
643       * specified in RFC 2252 to the provided buffer.
644       *
645       * @param  buffer              The buffer to which the information
646       *                             should be appended.
647       * @param  includeFileElement  Indicates whether to include an
648       *                             "extra" property that specifies the
649       *                             path to the schema file from which
650       *                             this name form was loaded.
651       */
652      public void toString(StringBuilder buffer,
653                           boolean includeFileElement)
654      {
655        buffer.append("( ");
656        buffer.append(oid);
657    
658        if (! names.isEmpty())
659        {
660          Iterator<String> iterator = names.values().iterator();
661    
662          String firstName = iterator.next();
663          if (iterator.hasNext())
664          {
665            buffer.append(" NAME ( '");
666            buffer.append(firstName);
667    
668            while (iterator.hasNext())
669            {
670              buffer.append("' '");
671              buffer.append(iterator.next());
672            }
673    
674            buffer.append("' )");
675          }
676          else
677          {
678            buffer.append(" NAME '");
679            buffer.append(firstName);
680            buffer.append("'");
681          }
682        }
683    
684        if ((description != null) && (description.length() > 0))
685        {
686          buffer.append(" DESC '");
687          buffer.append(description);
688          buffer.append("'");
689        }
690    
691        if (isObsolete)
692        {
693          buffer.append(" OBSOLETE");
694        }
695    
696        buffer.append(" OC ");
697        buffer.append(structuralClass.getNameOrOID());
698    
699        if (! requiredAttributes.isEmpty())
700        {
701          Iterator<AttributeType> iterator =
702               requiredAttributes.iterator();
703    
704          String firstName = iterator.next().getNameOrOID();
705          if (iterator.hasNext())
706          {
707            buffer.append(" MUST ( ");
708            buffer.append(firstName);
709    
710            while (iterator.hasNext())
711            {
712              buffer.append(" $ ");
713              buffer.append(iterator.next().getNameOrOID());
714            }
715    
716            buffer.append(" )");
717          }
718          else
719          {
720            buffer.append(" MUST ");
721            buffer.append(firstName);
722          }
723        }
724    
725        if (! optionalAttributes.isEmpty())
726        {
727          Iterator<AttributeType> iterator =
728               optionalAttributes.iterator();
729    
730          String firstName = iterator.next().getNameOrOID();
731          if (iterator.hasNext())
732          {
733            buffer.append(" MAY ( ");
734            buffer.append(firstName);
735    
736            while (iterator.hasNext())
737            {
738              buffer.append(" $ ");
739              buffer.append(iterator.next().getNameOrOID());
740            }
741    
742            buffer.append(" )");
743          }
744          else
745          {
746            buffer.append(" MAY ");
747            buffer.append(firstName);
748          }
749        }
750    
751        if (! extraProperties.isEmpty())
752        {
753          for (String property : extraProperties.keySet())
754          {
755            if ((! includeFileElement) &&
756                property.equals(SCHEMA_PROPERTY_FILENAME))
757            {
758              continue;
759            }
760    
761            List<String> valueList = extraProperties.get(property);
762    
763            buffer.append(" ");
764            buffer.append(property);
765    
766            if (valueList.size() == 1)
767            {
768              buffer.append(" '");
769              buffer.append(valueList.get(0));
770              buffer.append("'");
771            }
772            else
773            {
774              buffer.append(" ( ");
775    
776              for (String value : valueList)
777              {
778                buffer.append("'");
779                buffer.append(value);
780                buffer.append("' ");
781              }
782    
783              buffer.append(")");
784            }
785          }
786        }
787    
788        buffer.append(" )");
789      }
790    }
791