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.DITContentRuleSyntax;
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.Validator.*;
045    
046    
047    
048    /**
049     * This class defines a DIT content rule, which defines the set of
050     * allowed, required, and prohibited attributes for entries with a
051     * given structural objectclass, and also indicates which auxiliary
052     * classes that may be included in the entry.
053     */
054    @org.opends.server.types.PublicAPI(
055         stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
056         mayInstantiate=false,
057         mayExtend=false,
058         mayInvoke=true)
059    public final class DITContentRule
060           implements SchemaFileElement
061    {
062      /**
063       * The tracer object for the debug logger.
064       */
065      private static final DebugTracer TRACER = getTracer();
066    
067      // Indicates whether this content rule is declared "obsolete".
068      private final boolean isObsolete;
069    
070      // The set of additional name-value pairs associated with this
071      // content rule definition.
072      private final Map<String,List<String>> extraProperties;
073    
074      // The set of names for this DIT content rule, in a mapping between
075      // the all-lowercase form and the user-defined form.
076      private final Map<String,String> names;
077    
078      // The structural objectclass for this DIT content rule.
079      private final ObjectClass structuralClass;
080    
081      // The set of auxiliary objectclasses that entries with this content
082      // rule may contain, in a mapping between the objectclass and the
083      // user-defined name for that class.
084      private final Set<ObjectClass> auxiliaryClasses;
085    
086      // The set of optional attribute types for this DIT content rule.
087      private final Set<AttributeType> optionalAttributes;
088    
089      // The set of prohibited attribute types for this DIT content rule.
090      private final Set<AttributeType> prohibitedAttributes;
091    
092      // The set of required attribute types for this DIT content rule.
093      private final Set<AttributeType> requiredAttributes;
094    
095      // The definition string used to create this DIT content rule.
096      private final String definition;
097    
098      // The description for this DIT content rule.
099      private final String description;
100    
101    
102    
103      /**
104       * Creates a new DIT content rule definition with the provided
105       * information.
106       *
107       * @param  definition            The definition string used to
108       *                               create this DIT content rule.  It
109       *                               must not be {@code null}.
110       * @param  structuralClass       The structural objectclass for this
111       *                               DIT content rule.  It must not be
112       *                               {@code null}.
113       * @param  names                 The set of names that may be used
114       *                               to reference this DIT content rule.
115       * @param  description           The description for this DIT
116       *                               content rule.
117       * @param  auxiliaryClasses      The set of auxiliary classes for
118       *                               this DIT content rule
119       * @param  requiredAttributes    The set of required attribute types
120       *                               for this DIT content rule.
121       * @param  optionalAttributes    The set of optional attribute types
122       *                               for this DIT content rule.
123       * @param  prohibitedAttributes  The set of prohibited attribute
124       *                               types for this DIT content rule.
125       * @param  isObsolete            Indicates whether this DIT content
126       *                               rule is declared "obsolete".
127       * @param  extraProperties       A set of extra properties for this
128       *                               DIT content rule.
129       */
130      public DITContentRule(String definition,
131                            ObjectClass structuralClass,
132                            Map<String,String> names, String description,
133                            Set<ObjectClass> auxiliaryClasses,
134                            Set<AttributeType> requiredAttributes,
135                            Set<AttributeType> optionalAttributes,
136                            Set<AttributeType> prohibitedAttributes,
137                            boolean isObsolete,
138                            Map<String,List<String>> extraProperties)
139      {
140        ensureNotNull(definition, structuralClass);
141    
142        this.structuralClass = structuralClass;
143        this.description     = description;
144        this.isObsolete      = isObsolete;
145    
146        int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
147        if (schemaFilePos > 0)
148        {
149          String defStr;
150          try
151          {
152            int firstQuotePos = definition.indexOf('\'', schemaFilePos);
153            int secondQuotePos = definition.indexOf('\'',
154                                                    firstQuotePos+1);
155    
156            defStr = definition.substring(0, schemaFilePos).trim() + " " +
157                     definition.substring(secondQuotePos+1).trim();
158          }
159          catch (Exception e)
160          {
161            if (debugEnabled())
162            {
163              TRACER.debugCaught(DebugLogLevel.ERROR, e);
164            }
165    
166            defStr = definition;
167          }
168    
169          this.definition = defStr;
170        }
171        else
172        {
173          this.definition = definition;
174        }
175    
176        if ((names == null) || names.isEmpty())
177        {
178          this.names = new LinkedHashMap<String,String>(0);
179        }
180        else
181        {
182          this.names = new LinkedHashMap<String,String>(names);
183        }
184    
185        if ((auxiliaryClasses == null) || auxiliaryClasses.isEmpty())
186        {
187          this.auxiliaryClasses = new LinkedHashSet<ObjectClass>(0);
188        }
189        else
190        {
191          this.auxiliaryClasses =
192               new LinkedHashSet<ObjectClass>(auxiliaryClasses);
193        }
194    
195        if ((requiredAttributes == null) || requiredAttributes.isEmpty())
196        {
197          this.requiredAttributes = new LinkedHashSet<AttributeType>(0);
198        }
199        else
200        {
201          this.requiredAttributes =
202               new LinkedHashSet<AttributeType>(requiredAttributes);
203        }
204    
205        if ((optionalAttributes == null) || optionalAttributes.isEmpty())
206        {
207          this.optionalAttributes = new LinkedHashSet<AttributeType>(0);
208        }
209        else
210        {
211          this.optionalAttributes =
212               new LinkedHashSet<AttributeType>(optionalAttributes);
213        }
214    
215        if ((prohibitedAttributes == null) ||
216            prohibitedAttributes.isEmpty())
217        {
218          this.prohibitedAttributes = new LinkedHashSet<AttributeType>(0);
219        }
220        else
221        {
222          this.prohibitedAttributes =
223               new LinkedHashSet<AttributeType>(prohibitedAttributes);
224        }
225    
226        if ((extraProperties == null) || extraProperties.isEmpty())
227        {
228          this.extraProperties =
229               new LinkedHashMap<String,List<String>>(0);
230        }
231        else
232        {
233          this.extraProperties =
234               new LinkedHashMap<String,List<String>>(extraProperties);
235        }
236      }
237    
238    
239    
240      /**
241       * Retrieves the definition string used to create this DIT content
242       * rule.
243       *
244       * @return  The definition string used to create this DIT content
245       *          rule.
246       */
247      public String getDefinition()
248      {
249        return definition;
250      }
251    
252    
253    
254      /**
255       * Creates a new instance of this DIT content rule based on the
256       * definition string.  It will also preserve other state information
257       * associated with this DIT content rule that is not included in the
258       * definition string (e.g., the name of the schema file with which
259       * it is associated).
260       *
261       * @return  The new instance of this DIT content rule based on the
262       *          definition string.
263       *
264       * @throws  DirectoryException  If a problem occurs while attempting
265       *                              to create a new DIT content rule
266       *                              instance from the definition string.
267       */
268      public DITContentRule recreateFromDefinition()
269             throws DirectoryException
270      {
271        ByteString value  = ByteStringFactory.create(definition);
272        Schema     schema = DirectoryConfig.getSchema();
273    
274        DITContentRule dcr =
275             DITContentRuleSyntax.decodeDITContentRule(value, schema,
276                                                       false);
277        dcr.setSchemaFile(getSchemaFile());
278    
279        return dcr;
280      }
281    
282    
283    
284      /**
285       * Retrieves the structural objectclass for this DIT content rule.
286       *
287       * @return  The structural objectclass for this DIT content rule.
288       */
289      public ObjectClass getStructuralClass()
290      {
291        return structuralClass;
292      }
293    
294    
295    
296      /**
297       * Retrieves the set of names that may be used to reference this DIT
298       * content rule.  The returned object will be a mapping between each
299       * name in all lowercase characters and that name in a user-defined
300       * form (which may include mixed capitalization).
301       *
302       * @return  The set of names that may be used to reference this DIT
303       *          content rule.
304       */
305      public Map<String,String> getNames()
306      {
307        return names;
308      }
309    
310    
311    
312      /**
313       * Retrieves the primary name to use to reference this DIT content
314       * rule.
315       *
316       * @return  The primary name to use to reference this DIT content
317       *          rule, or {@code null} if there is none.
318       */
319      public String getName()
320      {
321        if (names.isEmpty())
322        {
323          return null;
324        }
325        else
326        {
327          return names.values().iterator().next();
328        }
329      }
330    
331    
332    
333      /**
334       * Indicates whether the provided lowercase name may be used to
335       * reference this DIT content rule.
336       *
337       * @param  lowerName  The name for which to make the determination,
338       *                    in all lowercase characters.
339       *
340       * @return  {@code true} if the provided lowercase name may be used
341       *          to reference this DIT content rule, or {@code false} if
342       *          not.
343       */
344      public boolean hasName(String lowerName)
345      {
346        return names.containsKey(lowerName);
347      }
348    
349    
350    
351      /**
352       * Retrieves the name of the schema file that contains the
353       * definition for this DIT content rule.
354       *
355       * @return  The name of the schema file that contains the definition
356       *          for this DIT content rule, or {@code null} if it is not
357       *          known or if it is not stored in any schema file.
358       */
359      public String getSchemaFile()
360      {
361        List<String> values =
362             extraProperties.get(SCHEMA_PROPERTY_FILENAME);
363        if ((values == null) || values.isEmpty())
364        {
365          return null;
366        }
367    
368        return values.get(0);
369      }
370    
371    
372    
373      /**
374       * Specifies the name of the schema file that contains the
375       * definition for this DIT content rule.
376       *
377       * @param  schemaFile  The name of the schema file that contains the
378       *                     definition for this DIT content rule.
379       */
380      public void setSchemaFile(String schemaFile)
381      {
382        setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
383      }
384    
385    
386    
387      /**
388       * Retrieves the description for this DIT content rule.
389       *
390       * @return  The description for this DIT content rule, or
391       *          {@code null} if there is none.
392       */
393      public String getDescription()
394      {
395        return description;
396      }
397    
398    
399    
400      /**
401       * Retrieves the set of auxiliary objectclasses that may be used for
402       * entries associated with this DIT content rule.
403       *
404       * @return  The set of auxiliary objectclasses that may be used for
405       *          entries associated with this DIT content rule.
406       */
407      public Set<ObjectClass> getAuxiliaryClasses()
408      {
409        return auxiliaryClasses;
410      }
411    
412    
413    
414      /**
415       * Indicates whether the provided auxiliary objectclass is allowed
416       * for use by this DIT content rule.
417       *
418       * @param  auxiliaryClass  The auxiliary objectclass for which to
419       *                         make the determination.
420       *
421       * @return  {@code true} if the provided auxiliary objectclass is
422       *          allowed for use by this DIT content rule, or
423       *          {@code false} if not.
424       */
425      public boolean isAllowedAuxiliaryClass(ObjectClass auxiliaryClass)
426      {
427        return auxiliaryClasses.contains(auxiliaryClass);
428      }
429    
430    
431    
432      /**
433       * Retrieves the set of required attributes for this DIT content
434       * rule.
435       *
436       * @return  The set of required attributes for this DIT content
437       *          rule.
438       */
439      public Set<AttributeType> getRequiredAttributes()
440      {
441        return requiredAttributes;
442      }
443    
444    
445    
446      /**
447       * Indicates whether the provided attribute type is included in the
448       * required attribute list for this DIT content rule.
449       *
450       * @param  attributeType  The attribute type for which to make the
451       *                        determination.
452       *
453       * @return  {@code true} if the provided attribute type is required
454       *          by this DIT content rule, or {@code false} if not.
455       */
456      public boolean isRequired(AttributeType attributeType)
457      {
458        return requiredAttributes.contains(attributeType);
459      }
460    
461    
462    
463      /**
464       * Retrieves the set of optional attributes for this DIT content
465       * rule.
466       *
467       * @return  The set of optional attributes for this DIT content
468       *          rule.
469       */
470      public Set<AttributeType> getOptionalAttributes()
471      {
472        return optionalAttributes;
473      }
474    
475    
476    
477      /**
478       * Indicates whether the provided attribute type is included in the
479       * optional attribute list for this DIT content rule.
480       *
481       * @param  attributeType  The attribute type for which to make the
482       *                        determination.
483       *
484       * @return  {@code true} if the provided attribute type is optional
485       *          for this DIT content rule, or {@code false} if not.
486       */
487      public boolean isOptional(AttributeType attributeType)
488      {
489        return optionalAttributes.contains(attributeType);
490      }
491    
492    
493    
494      /**
495       * Indicates whether the provided attribute type is in the list of
496       * required or optional attributes for this DIT content rule.
497       *
498       * @param  attributeType  The attribute type for which to make the
499       *                        determination.
500       *
501       * @return  {@code true} if the provided attribute type is required
502       *          or allowed for this DIT content rule, or {@code false}
503       *          if it is not.
504       */
505      public boolean isRequiredOrOptional(AttributeType attributeType)
506      {
507        return (requiredAttributes.contains(attributeType) ||
508                optionalAttributes.contains(attributeType));
509      }
510    
511    
512    
513      /**
514       * Indicates whether the provided attribute type is in the list of
515       * required or optional attributes for this DIT content rule.
516       *
517       * @param  attributeType  The attribute type for which to make the
518       *                        determination.
519       * @param  acceptEmpty    Indicates whether an empty list of
520       *                        required or optional attributes should be
521       *                        taken to indicate that all attributes
522       *                        allowed for an objectclass will be
523       *                        acceptable.
524       *
525       * @return  {@code true} if the provided attribute type is required
526       *          or allowed for this DIT content rule, or {@code false}
527       *          if it is not.
528       */
529      public boolean isRequiredOrOptional(AttributeType attributeType,
530                                          boolean acceptEmpty)
531      {
532        if (acceptEmpty &&
533            (requiredAttributes.isEmpty() ||
534             optionalAttributes.isEmpty()))
535        {
536          return true;
537        }
538    
539        return (requiredAttributes.contains(attributeType) ||
540                optionalAttributes.contains(attributeType));
541      }
542    
543    
544    
545      /**
546       * Retrieves the set of prohibited attributes for this DIT content
547       * rule.
548       *
549       * @return  The set of prohibited attributes for this DIT content
550       *          rule.
551       */
552      public Set<AttributeType> getProhibitedAttributes()
553      {
554        return prohibitedAttributes;
555      }
556    
557    
558    
559      /**
560       * Indicates whether the provided attribute type is included in the
561       * prohibited attribute list for this DIT content rule.
562       *
563       * @param  attributeType  The attribute type for which to make the
564       *                        determination.
565       *
566       * @return  {@code true} if the provided attribute type is
567       *          prohibited for this DIT content rule, or {@code false}
568       *          if not.
569       */
570      public boolean isProhibited(AttributeType attributeType)
571      {
572        return prohibitedAttributes.contains(attributeType);
573      }
574    
575    
576    
577      /**
578       * Indicates whether this DIT content rule is declared "obsolete".
579       *
580       * @return  {@code true} if this DIT content rule is declared
581       *          "obsolete", or {@code false} if it is not.
582       */
583      public boolean isObsolete()
584      {
585        return isObsolete;
586      }
587    
588    
589    
590      /**
591       * Retrieves a mapping between the names of any extra non-standard
592       * properties that may be associated with this DIT content rule and
593       * the value for that property.
594       *
595       * @return  A mapping between the names of any extra non-standard
596       *          properties that may be associated with this DIT content
597       *          rule and the value for that property.
598       */
599      public Map<String,List<String>> getExtraProperties()
600      {
601        return extraProperties;
602      }
603    
604    
605    
606      /**
607       * Retrieves the value of the specified "extra" property for this
608       * DIT content rule.
609       *
610       * @param  propertyName  The name of the "extra" property for which
611       *                       to retrieve the value.
612       *
613       * @return  The value of the specified "extra" property for this DIT
614       *          content rule, or {@code null} if no such property is
615       *          defined.
616       */
617      public List<String> getExtraProperty(String propertyName)
618      {
619        return extraProperties.get(propertyName);
620      }
621    
622    
623    
624      /**
625       * Specifies the provided "extra" property for this DIT content
626       * rule.
627       *
628       * @param  name   The name for the "extra" property.  It must not be
629       *                {@code null}.
630       * @param  value  The value for the "extra" property, or
631       *                {@code null} if the property is to be removed.
632       */
633      public void setExtraProperty(String name, String value)
634      {
635        ensureNotNull(name);
636    
637        if (value == null)
638        {
639          extraProperties.remove(name);
640        }
641        else
642        {
643          LinkedList<String> values = new LinkedList<String>();
644          values.add(value);
645    
646          extraProperties.put(name, values);
647        }
648      }
649    
650    
651    
652      /**
653       * Specifies the provided "extra" property for this DIT content
654       * rule.
655       *
656       * @param  name    The name for the "extra" property.  It must not
657       *                 be {@code null}.
658       * @param  values  The set of value for the "extra" property, or
659       *                 {@code null} if the property is to be removed.
660       */
661      public void setExtraProperty(String name, List<String> values)
662      {
663        ensureNotNull(name);
664    
665        if ((values == null) || values.isEmpty())
666        {
667          extraProperties.remove(name);
668        }
669        else
670        {
671          LinkedList<String> valuesCopy = new LinkedList<String>(values);
672          extraProperties.put(name, valuesCopy);
673        }
674      }
675    
676    
677    
678      /**
679       * Indicates whether the provided object is equal to this DIT
680       * content rule.  The object will be considered equal if it is a DIT
681       * content rule for the same structural objectclass and the same
682       * sets of names.  For performance reasons, the set of auxiliary
683       * classes, and the sets of required, optional, and prohibited
684       * attribute types will not be checked, so that should be done
685       * manually if a more thorough equality comparison is required.
686       *
687       * @param  o  The object for which to make the determination.
688       *
689       * @return  {@code true} if the provided object is equal to
690       *          this DIT content rule, or {@code false} if not.
691       */
692      public boolean equals(Object o)
693      {
694        if (this == o)
695        {
696          return true;
697        }
698    
699        if ((o == null) || (! (o instanceof DITContentRule)))
700        {
701          return false;
702        }
703    
704        DITContentRule dcr = (DITContentRule) o;
705        if (! structuralClass.equals(dcr.structuralClass))
706        {
707          return false;
708        }
709    
710        if (names.size() != dcr.names.size())
711        {
712          return false;
713        }
714    
715        Iterator<String> iterator = names.keySet().iterator();
716        while (iterator.hasNext())
717        {
718          if (! dcr.names.containsKey(iterator.next()))
719          {
720            return false;
721          }
722        }
723    
724        return true;
725      }
726    
727    
728    
729      /**
730       * Retrieves the hash code for this DIT content rule.  It will be
731       * equal to the hash code for the associated structural objectclass.
732       *
733       * @return  The hash code for this DIT content rule.
734       */
735      public int hashCode()
736      {
737        return structuralClass.hashCode();
738      }
739    
740    
741    
742      /**
743       * Retrieves the string representation of this DIT content rule in
744       * the form specified in RFC 2252.
745       *
746       * @return  The string representation of this DIT content rule in
747       *          the form specified in RFC 2252.
748       */
749      public String toString()
750      {
751        StringBuilder buffer = new StringBuilder();
752        toString(buffer, true);
753        return buffer.toString();
754      }
755    
756    
757    
758      /**
759       * Appends a string representation of this attribute type in the
760       * form specified in RFC 2252 to the provided buffer.
761       *
762       * @param  buffer              The buffer to which the information
763       *                             should be appended.
764       * @param  includeFileElement  Indicates whether to include an
765       *                             "extra" property that specifies the
766       *                             path to the schema file from which
767       *                             this DIT content rule was loaded.
768       */
769      public void toString(StringBuilder buffer,
770      boolean includeFileElement)
771      {
772        buffer.append("( ");
773        buffer.append(structuralClass.getOID());
774    
775        if (! names.isEmpty())
776        {
777          Iterator<String> iterator = names.values().iterator();
778    
779          String firstName = iterator.next();
780          if (iterator.hasNext())
781          {
782            buffer.append(" NAME ( '");
783            buffer.append(firstName);
784    
785            while (iterator.hasNext())
786            {
787              buffer.append("' '");
788              buffer.append(iterator.next());
789            }
790    
791            buffer.append("' )");
792          }
793          else
794          {
795            buffer.append(" NAME '");
796            buffer.append(firstName);
797            buffer.append("'");
798          }
799        }
800    
801        if ((description != null) && (description.length() > 0))
802        {
803          buffer.append(" DESC '");
804          buffer.append(description);
805          buffer.append("'");
806        }
807    
808        if (isObsolete)
809        {
810          buffer.append(" OBSOLETE");
811        }
812    
813        if (! auxiliaryClasses.isEmpty())
814        {
815          Iterator<ObjectClass> iterator = auxiliaryClasses.iterator();
816    
817          String firstClass = iterator.next().getNameOrOID();
818          if (iterator.hasNext())
819          {
820            buffer.append(" AUX (");
821            buffer.append(firstClass);
822    
823            while (iterator.hasNext())
824            {
825              buffer.append(" $ ");
826              buffer.append(iterator.next());
827            }
828    
829            buffer.append(" )");
830          }
831          else
832          {
833            buffer.append(" AUX ");
834            buffer.append(firstClass);
835          }
836        }
837    
838        if (! requiredAttributes.isEmpty())
839        {
840          Iterator<AttributeType> iterator =
841               requiredAttributes.iterator();
842    
843          String firstName = iterator.next().getNameOrOID();
844          if (iterator.hasNext())
845          {
846            buffer.append(" MUST ( ");
847            buffer.append(firstName);
848    
849            while (iterator.hasNext())
850            {
851              buffer.append(" $ ");
852              buffer.append(iterator.next().getNameOrOID());
853            }
854    
855            buffer.append(" )");
856          }
857          else
858          {
859            buffer.append(" MUST ");
860            buffer.append(firstName);
861          }
862        }
863    
864        if (! optionalAttributes.isEmpty())
865        {
866          Iterator<AttributeType> iterator =
867               optionalAttributes.iterator();
868    
869          String firstName = iterator.next().getNameOrOID();
870          if (iterator.hasNext())
871          {
872            buffer.append(" MAY ( ");
873            buffer.append(firstName);
874    
875            while (iterator.hasNext())
876            {
877              buffer.append(" $ ");
878              buffer.append(iterator.next().getNameOrOID());
879            }
880    
881            buffer.append(" )");
882          }
883          else
884          {
885            buffer.append(" MAY ");
886            buffer.append(firstName);
887          }
888        }
889    
890        if (! prohibitedAttributes.isEmpty())
891        {
892          Iterator<AttributeType> iterator =
893               prohibitedAttributes.iterator();
894    
895          String firstName = iterator.next().getNameOrOID();
896          if (iterator.hasNext())
897          {
898            buffer.append(" NOT ( ");
899            buffer.append(firstName);
900    
901            while (iterator.hasNext())
902            {
903              buffer.append(" $ ");
904              buffer.append(iterator.next().getNameOrOID());
905            }
906    
907            buffer.append(" )");
908          }
909          else
910          {
911            buffer.append(" NOT ");
912            buffer.append(firstName);
913          }
914        }
915    
916        if (! extraProperties.isEmpty())
917        {
918          for (String property : extraProperties.keySet())
919          {
920            if ((! includeFileElement) &&
921                property.equals(SCHEMA_PROPERTY_FILENAME))
922            {
923              continue;
924            }
925    
926            List<String> valueList = extraProperties.get(property);
927    
928            buffer.append(" ");
929            buffer.append(property);
930    
931            if (valueList.size() == 1)
932            {
933              buffer.append(" '");
934              buffer.append(valueList.get(0));
935              buffer.append("'");
936            }
937            else
938            {
939              buffer.append(" ( ");
940    
941              for (String value : valueList)
942              {
943                buffer.append("'");
944                buffer.append(value);
945                buffer.append("' ");
946              }
947    
948              buffer.append(")");
949            }
950          }
951        }
952    
953        buffer.append(" )");
954      }
955    }
956