001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.shared.ldap.schema;
021    
022    
023    import java.util.HashSet;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Set;
027    import java.util.UUID;
028    
029    import javax.naming.NamingException;
030    
031    import org.apache.directory.shared.ldap.entry.Entry;
032    import org.apache.directory.shared.ldap.entry.EntryAttribute;
033    import org.apache.directory.shared.ldap.entry.Modification;
034    import org.apache.directory.shared.ldap.entry.Value;
035    import org.apache.directory.shared.ldap.schema.parsers.AbstractAdsSchemaDescription;
036    import org.apache.directory.shared.ldap.schema.parsers.AbstractSchemaDescription;
037    import org.apache.directory.shared.ldap.schema.parsers.AttributeTypeDescription;
038    import org.apache.directory.shared.ldap.util.StringTools;
039    
040    
041    /**
042     * Various utility methods for schema functions and objects.
043     * 
044     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045     * @version $Rev: 780471 $
046     */
047    public class SchemaUtils
048    {
049        /**
050         * Gets the target entry as it would look after a modification operation 
051         * were performed on it.
052         * 
053         * @param mods the modifications performed on the entry
054         * @param entry the source entry that is modified
055         * @return the resultant entry after the modifications have taken place
056         * @throws NamingException if there are problems accessing attributes
057         */
058        public static Entry getTargetEntry( List<? extends Modification> mods, Entry entry )
059            throws NamingException
060        {
061            Entry targetEntry = entry.clone();
062    
063            for ( Modification mod : mods )
064            {
065                String id = mod.getAttribute().getId();
066    
067                switch ( mod.getOperation() )
068                {
069                    case REPLACE_ATTRIBUTE :
070                        targetEntry.put( mod.getAttribute() );
071                        break;
072    
073                    case ADD_ATTRIBUTE :
074                        EntryAttribute combined = mod.getAttribute().clone();
075                        EntryAttribute toBeAdded = mod.getAttribute();
076                        EntryAttribute existing = entry.get( id );
077    
078                        if ( existing != null )
079                        {
080                            for ( Value<?> value:existing )
081                            {
082                                combined.add( value );
083                            }
084                        }
085    
086                        for ( Value<?> value:toBeAdded )
087                        {
088                            combined.add( value );
089                        }
090    
091                        targetEntry.put( combined );
092                        break;
093    
094                    case REMOVE_ATTRIBUTE :
095                        EntryAttribute toBeRemoved = mod.getAttribute();
096    
097                        if ( toBeRemoved.size() == 0 )
098                        {
099                            targetEntry.removeAttributes( id );
100                        }
101                        else
102                        {
103                            existing = targetEntry.get( id );
104    
105                            if ( existing != null )
106                            {
107                                for ( Value<?> value:toBeRemoved )
108                                {
109                                    existing.remove( value );
110                                }
111                            }
112                        }
113    
114                        break;
115    
116                    default:
117                        throw new IllegalStateException( "undefined modification type: " + mod.getOperation() );
118                }
119            }
120    
121            return targetEntry;
122        }
123    
124    
125        // ------------------------------------------------------------------------
126        // qdescrs rendering operations
127        // ------------------------------------------------------------------------
128    
129        /**
130         * Renders qdescrs into an existing buffer.
131         * 
132         * @param buf
133         *            the string buffer to render the quoted description strs into
134         * @param qdescrs
135         *            the quoted description strings to render
136         * @return the same string buffer that was given for call chaining
137         */
138        public static StringBuffer render( StringBuffer buf, String[] qdescrs )
139        {
140            if ( qdescrs == null || qdescrs.length == 0 )
141            {
142                return buf;
143            }
144            else if ( qdescrs.length == 1 )
145            {
146                buf.append( "'" ).append( qdescrs[0] ).append( "'" );
147            }
148            else
149            {
150                buf.append( "( " );
151                for ( int ii = 0; ii < qdescrs.length; ii++ )
152                {
153                    buf.append( "'" ).append( qdescrs[ii] ).append( "' " );
154                }
155                buf.append( ")" );
156            }
157    
158            return buf;
159        }
160    
161    
162        /**
163         * Renders qdescrs into a new buffer.
164         * 
165         * @param qdescrs
166         *            the quoted description strings to render
167         * @return the string buffer the qdescrs are rendered into
168         */
169        public static StringBuffer render( String[] qdescrs )
170        {
171            StringBuffer buf = new StringBuffer();
172            return render( buf, qdescrs );
173        }
174    
175    
176        // ------------------------------------------------------------------------
177        // objectClass list rendering operations
178        // ------------------------------------------------------------------------
179    
180        /**
181         * Renders a list of object classes for things like a list of superior
182         * objectClasses using the ( oid $ oid ) format.
183         * 
184         * @param ocs
185         *            the objectClasses to list
186         * @return a buffer which contains the rendered list
187         */
188        public static StringBuffer render( ObjectClass[] ocs )
189        {
190            StringBuffer buf = new StringBuffer();
191            return render( buf, ocs );
192        }
193    
194    
195        /**
196         * Renders a list of object classes for things like a list of superior
197         * objectClasses using the ( oid $ oid ) format into an existing buffer.
198         * 
199         * @param buf
200         *            the string buffer to render the list of objectClasses into
201         * @param ocs
202         *            the objectClasses to list
203         * @return a buffer which contains the rendered list
204         */
205        public static StringBuffer render( StringBuffer buf, ObjectClass[] ocs )
206        {
207            if ( ocs == null || ocs.length == 0 )
208            {
209                return buf;
210            }
211            else if ( ocs.length == 1 )
212            {
213                buf.append( ocs[0].getName() );
214            }
215            else
216            {
217                buf.append( "( " );
218                for ( int ii = 0; ii < ocs.length; ii++ )
219                {
220                    if ( ii + 1 < ocs.length )
221                    {
222                        buf.append( ocs[ii].getName() ).append( " $ " );
223                    }
224                    else
225                    {
226                        buf.append( ocs[ii].getName() );
227                    }
228                }
229                buf.append( " )" );
230            }
231    
232            return buf;
233        }
234    
235    
236        // ------------------------------------------------------------------------
237        // attributeType list rendering operations
238        // ------------------------------------------------------------------------
239    
240        /**
241         * Renders a list of attributeTypes for things like the must or may list of
242         * objectClasses using the ( oid $ oid ) format.
243         * 
244         * @param ats
245         *            the attributeTypes to list
246         * @return a buffer which contains the rendered list
247         */
248        public static StringBuffer render( AttributeType[] ats )
249        {
250            StringBuffer buf = new StringBuffer();
251            return render( buf, ats );
252        }
253    
254    
255        /**
256         * Renders a list of attributeTypes for things like the must or may list of
257         * objectClasses using the ( oid $ oid ) format into an existing buffer.
258         * 
259         * @param buf
260         *            the string buffer to render the list of attributeTypes into
261         * @param ats
262         *            the attributeTypes to list
263         * @return a buffer which contains the rendered list
264         */
265        public static StringBuffer render( StringBuffer buf, AttributeType[] ats )
266        {
267            if ( ats == null || ats.length == 0 )
268            {
269                return buf;
270            }
271            else if ( ats.length == 1 )
272            {
273                buf.append( ats[0].getName() );
274            }
275            else
276            {
277                buf.append( "( " );
278                for ( int ii = 0; ii < ats.length; ii++ )
279                {
280                    if ( ii + 1 < ats.length )
281                    {
282                        buf.append( ats[ii].getName() ).append( " $ " );
283                    }
284                    else
285                    {
286                        buf.append( ats[ii].getName() );
287                    }
288                }
289                buf.append( " )" );
290            }
291    
292            return buf;
293        }
294    
295    
296        // ------------------------------------------------------------------------
297        // schema object rendering operations
298        // ------------------------------------------------------------------------
299    
300        /**
301         * Renders an objectClass into a new StringBuffer according to the Object
302         * Class Description Syntax 1.3.6.1.4.1.1466.115.121.1.37. The syntax is
303         * described in detail within section 4.1.1. of LDAPBIS [<a
304         * href="http://ietf.org/internet-drafts/draft-ietf-ldapbis-models-12.txt">MODELS</a>]
305         * which is replicated here for convenience:
306         * 
307         * <pre>
308         *  4.1.1. Object Class Definitions
309         * 
310         *   Object Class definitions are written according to the ABNF:
311         * 
312         *     ObjectClassDescription = LPAREN WSP
313         *         numericoid                 ; object identifier
314         *         [ SP &quot;NAME&quot; SP qdescrs ]   ; short names (descriptors)
315         *         [ SP &quot;DESC&quot; SP qdstring ]  ; description
316         *         [ SP &quot;OBSOLETE&quot; ]          ; not active
317         *         [ SP &quot;SUP&quot; SP oids ]       ; superior object classes
318         *         [ SP kind ]                ; kind of class
319         *         [ SP &quot;MUST&quot; SP oids ]      ; attribute types
320         *         [ SP &quot;MAY&quot; SP oids ]       ; attribute types
321         *         extensions WSP RPAREN
322         * 
323         *     kind = &quot;ABSTRACT&quot; / &quot;STRUCTURAL&quot; / &quot;AUXILIARY&quot;
324         * 
325         *   where:
326         *     &lt;numericoid&gt; is object identifier assigned to this object class;
327         *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this object
328         *         class;
329         *     DESC &lt;qdstring&gt; is a short descriptive string;
330         *     OBSOLETE indicates this object class is not active;
331         *     SUP &lt;oids&gt; specifies the direct superclasses of this object class;
332         *     the kind of object class is indicated by one of ABSTRACT,
333         *         STRUCTURAL, or AUXILIARY, default is STRUCTURAL;
334         *     MUST and MAY specify the sets of required and allowed attribute
335         *         types, respectively; and
336         *     &lt;extensions&gt; describe extensions.
337         * </pre>
338         * @param oc the objectClass to render the description of
339         * @return the buffer containing the objectClass description
340         * @throws NamingException if there are any problems accessing objectClass
341         * information
342         */
343        public static StringBuffer render( ObjectClass oc ) throws NamingException
344        {
345            StringBuffer buf = new StringBuffer();
346            buf.append( "( " ).append( oc.getOid() );
347    
348            if ( oc.getNamesRef() != null && oc.getNamesRef().length > 0 )
349            {
350                buf.append( " NAME " );
351                render( buf, oc.getNamesRef() ).append( " " );
352            }
353            else
354            {
355                buf.append( " " );
356            }
357    
358            if ( oc.getDescription() != null )
359            {
360                buf.append( "DESC " ).append( "'" ).append( oc.getDescription() ).append( "' " );
361            }
362    
363            if ( oc.isObsolete() )
364            {
365                buf.append( " OBSOLETE " );
366            }
367    
368            if ( oc.getSuperClasses() != null && oc.getSuperClasses().length > 0 )
369            {
370                buf.append( "SUP " );
371                render( buf, oc.getSuperClasses() );
372            }
373    
374            if ( oc.getType() != null )
375            {
376                buf.append( " " ).append( oc.getType() );
377            }
378    
379            if ( oc.getMustList() != null && oc.getMustList().length > 0 )
380            {
381                buf.append( " MUST " );
382                render( buf, oc.getMustList() );
383            }
384    
385            if ( oc.getMayList() != null && oc.getMayList().length > 0 )
386            {
387                buf.append( " MAY " );
388                render( buf, oc.getMayList() );
389            }
390    
391            buf.append( " X-SCHEMA '" );
392            buf.append( oc.getSchema() );
393            buf.append( "'" );
394    
395            // @todo extensions are not presently supported and skipped
396            // the extensions would go here before closing off the description
397    
398            buf.append( " )" );
399    
400            return buf;
401        }
402    
403    
404        /**
405         * Renders an attributeType into a new StringBuffer according to the
406         * Attribute Type Description Syntax 1.3.6.1.4.1.1466.115.121.1.3. The
407         * syntax is described in detail within section 4.1.2. of LDAPBIS [<a
408         * href="http://ietf.org/internet-drafts/draft-ietf-ldapbis-models-12.txt">MODELS</a>]
409         * which is replicated here for convenience:
410         * 
411         * <pre>
412         *  4.1.2. Attribute Types
413         * 
414         *   Attribute Type definitions are written according to the ABNF:
415         * 
416         *   AttributeTypeDescription = LPAREN WSP
417         *         numericoid                    ; object identifier
418         *         [ SP &quot;NAME&quot; SP qdescrs ]      ; short names (descriptors)
419         *         [ SP &quot;DESC&quot; SP qdstring ]     ; description
420         *         [ SP &quot;OBSOLETE&quot; ]             ; not active
421         *         [ SP &quot;SUP&quot; SP oid ]           ; supertype
422         *         [ SP &quot;EQUALITY&quot; SP oid ]      ; equality matching rule
423         *         [ SP &quot;ORDERING&quot; SP oid ]      ; ordering matching rule
424         *         [ SP &quot;SUBSTR&quot; SP oid ]        ; substrings matching rule
425         *         [ SP &quot;SYNTAX&quot; SP noidlen ]    ; value syntax
426         *         [ SP &quot;SINGLE-VALUE&quot; ]         ; single-value
427         *         [ SP &quot;COLLECTIVE&quot; ]           ; collective
428         *         [ SP &quot;NO-USER-MODIFICATION&quot; ] ; not user modifiable
429         *         [ SP &quot;USAGE&quot; SP usage ]       ; usage
430         *         extensions WSP RPAREN         ; extensions
431         * 
432         *     usage = &quot;userApplications&quot;     /  ; user
433         *             &quot;directoryOperation&quot;   /  ; directory operational
434         *             &quot;distributedOperation&quot; /  ; DSA-shared operational
435         *             &quot;dSAOperation&quot;            ; DSA-specific operational
436         * 
437         *   where:
438         *     &lt;numericoid&gt; is object identifier assigned to this attribute type;
439         *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this
440         *         attribute type;
441         *     DESC &lt;qdstring&gt; is a short descriptive string;
442         *     OBSOLETE indicates this attribute type is not active;
443         *     SUP oid specifies the direct supertype of this type;
444         *     EQUALITY, ORDERING, SUBSTR provide the oid of the equality,
445         *         ordering, and substrings matching rules, respectively;
446         *     SYNTAX identifies value syntax by object identifier and may suggest
447         *         a minimum upper bound;
448         *     SINGLE-VALUE indicates attributes of this type are restricted to a
449         *         single value;
450         *     COLLECTIVE indicates this attribute type is collective
451         *         [X.501][RFC3671];
452         *     NO-USER-MODIFICATION indicates this attribute type is not user
453         *         modifiable;
454         *     USAGE indicates the application of this attribute type; and
455         *     &lt;extensions&gt; describe extensions.
456         * </pre>
457         * @param at the AttributeType to render the description for
458         * @return the StringBuffer containing the rendered attributeType description
459         * @throws NamingException if there are problems accessing the objects
460         * associated with the attribute type.
461         */
462        public static StringBuffer render( AttributeType at ) throws NamingException
463        {
464            StringBuffer buf = new StringBuffer();
465            buf.append( "( " ).append( at.getOid() );
466    
467            if ( at.getNamesRef() != null && at.getNamesRef().length > 0 )
468            {
469                buf.append( " NAME " );
470                render( buf, at.getNamesRef() ).append( " " );
471            }
472            else
473            {
474                buf.append( " " );
475            }
476    
477            if ( at.getDescription() != null )
478            {
479                buf.append( "DESC " ).append( "'" ).append( at.getDescription() ).append( "' " );
480            }
481    
482            if ( at.isObsolete() )
483            {
484                buf.append( " OBSOLETE" );
485            }
486    
487            if ( at.getSuperior() != null )
488            {
489                buf.append( " SUP " ).append( at.getSuperior().getName() );
490            }
491    
492            if ( at.getEquality() != null )
493            {
494                buf.append( " EQUALITY " ).append( at.getEquality().getName() );
495            }
496    
497            if ( at.getOrdering() != null )
498            {
499                buf.append( " ORDERING " ).append( at.getOrdering().getName() );
500            }
501    
502            if ( at.getSubstr() != null )
503            {
504                buf.append( " SUBSTR " ).append( at.getSubstr().getName() );
505            }
506    
507            if ( at.getSyntax() != null )
508            {
509                buf.append( " SYNTAX " ).append( at.getSyntax().getOid() );
510    
511                if ( at.getLength() > 0 )
512                {
513                    buf.append( "{" ).append( at.getLength() ).append( "}" );
514                }
515            }
516    
517            if ( at.isSingleValue() )
518            {
519                buf.append( " SINGLE-VALUE" );
520            }
521    
522            if ( at.isCollective() )
523            {
524                buf.append( " COLLECTIVE" );
525            }
526    
527            if ( !at.isCanUserModify() )
528            {
529                buf.append( " NO-USER-MODIFICATION" );
530            }
531    
532            if ( at.getUsage() != null )
533            {
534                buf.append( " USAGE " ).append( UsageEnum.render( at.getUsage() ) );
535            }
536    
537            buf.append( " X-SCHEMA '" );
538            buf.append( at.getSchema() );
539            buf.append( "'" );
540    
541            // @todo extensions are not presently supported and skipped
542            // the extensions would go here before closing off the description
543    
544            buf.append( " )" );
545    
546            return buf;
547        }
548    
549    
550        /**
551         * Renders an attributeType description object into a new StringBuffer
552         * according to the Attribute Type Description Syntax defined in MODELS
553         * 1.3.6.1.4.1.1466.115.121.1.3. The syntax is described in detail within
554         * section 4.1.2. of (@TODO NEEDS TO CHANGE SINCE THIS IS NOW AN RFC) LDAPBIS [<a
555         * href="http://ietf.org/internet-drafts/draft-ietf-ldapbis-models-12.txt">MODELS</a>]
556         * which is replicated here for convenience:
557         *
558         * <pre>
559         *  4.1.2. Attribute Types
560         *
561         *   Attribute Type definitions are written according to the ABNF:
562         *
563         *   AttributeTypeDescription = LPAREN WSP
564         *         numericoid                    ; object identifier
565         *         [ SP &quot;NAME&quot; SP qdescrs ]      ; short names (descriptors)
566         *         [ SP &quot;DESC&quot; SP qdstring ]     ; description
567         *         [ SP &quot;OBSOLETE&quot; ]             ; not active
568         *         [ SP &quot;SUP&quot; SP oid ]           ; supertype
569         *         [ SP &quot;EQUALITY&quot; SP oid ]      ; equality matching rule
570         *         [ SP &quot;ORDERING&quot; SP oid ]      ; ordering matching rule
571         *         [ SP &quot;SUBSTR&quot; SP oid ]        ; substrings matching rule
572         *         [ SP &quot;SYNTAX&quot; SP noidlen ]    ; value syntax
573         *         [ SP &quot;SINGLE-VALUE&quot; ]         ; single-value
574         *         [ SP &quot;COLLECTIVE&quot; ]           ; collective
575         *         [ SP &quot;NO-USER-MODIFICATION&quot; ] ; not user modifiable
576         *         [ SP &quot;USAGE&quot; SP usage ]       ; usage
577         *         extensions WSP RPAREN         ; extensions
578         *
579         *     usage = &quot;userApplications&quot;     /  ; user
580         *             &quot;directoryOperation&quot;   /  ; directory operational
581         *             &quot;distributedOperation&quot; /  ; DSA-shared operational
582         *             &quot;dSAOperation&quot;            ; DSA-specific operational
583         *
584         *   where:
585         *     &lt;numericoid&gt; is object identifier assigned to this attribute type;
586         *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this
587         *         attribute type;
588         *     DESC &lt;qdstring&gt; is a short descriptive string;
589         *     OBSOLETE indicates this attribute type is not active;
590         *     SUP oid specifies the direct supertype of this type;
591         *     EQUALITY, ORDERING, SUBSTR provide the oid of the equality,
592         *         ordering, and substrings matching rules, respectively;
593         *     SYNTAX identifies value syntax by object identifier and may suggest
594         *         a minimum upper bound;
595         *     SINGLE-VALUE indicates attributes of this type are restricted to a
596         *         single value;
597         *     COLLECTIVE indicates this attribute type is collective
598         *         [X.501][RFC3671];
599         *     NO-USER-MODIFICATION indicates this attribute type is not user
600         *         modifiable;
601         *     USAGE indicates the application of this attribute type; and
602         *     &lt;extensions&gt; describe extensions.
603         * </pre>
604         * @param atd the AttributeTypeDescription to render the description for
605         * @return the StringBuffer containing the rendered attributeType description
606         * @throws NamingException if there are problems accessing the objects
607         * associated with the attribute type.
608         */
609        public static StringBuffer render( AttributeTypeDescription atd )
610        {
611            StringBuffer buf = new StringBuffer();
612            buf.append( "( " ).append( atd.getNumericOid() );
613    
614            if ( atd.getNames() != null && atd.getNames().size() > 0 )
615            {
616                buf.append( " NAME " );
617                render( buf, atd.getNames().toArray( new String[atd.getNames().size()] ) ).append( " " );
618            }
619            else
620            {
621                buf.append( " " );
622            }
623    
624            if ( atd.getDescription() != null )
625            {
626                buf.append( "DESC " ).append( "'" ).append( atd.getDescription() ).append( "' " );
627            }
628    
629            if ( atd.isObsolete() )
630            {
631                buf.append( " OBSOLETE" );
632            }
633    
634            if ( atd.getSuperType() != null )
635            {
636                buf.append( " SUP " ).append( atd.getSuperType() );
637            }
638    
639            if ( atd.getEqualityMatchingRule() != null )
640            {
641                buf.append( " EQUALITY " ).append( atd.getEqualityMatchingRule() );
642            }
643    
644            if ( atd.getOrderingMatchingRule() != null )
645            {
646                buf.append( " ORDERING " ).append( atd.getOrderingMatchingRule() );
647            }
648    
649            if ( atd.getSubstringsMatchingRule() != null )
650            {
651                buf.append( " SUBSTR " ).append( atd.getSubstringsMatchingRule() );
652            }
653    
654            if ( atd.getSyntax() != null )
655            {
656                buf.append( " SYNTAX " ).append( atd.getSyntax() );
657    
658                if ( atd.getSyntaxLength() > 0 )
659                {
660                    buf.append( "{" ).append( atd.getSyntaxLength() ).append( "}" );
661                }
662            }
663    
664            if ( atd.isSingleValued() )
665            {
666                buf.append( " SINGLE-VALUE" );
667            }
668    
669            if ( atd.isCollective() )
670            {
671                buf.append( " COLLECTIVE" );
672            }
673    
674            if ( !atd.isUserModifiable() )
675            {
676                buf.append( " NO-USER-MODIFICATION" );
677            }
678    
679            if ( atd.getUsage() != null )
680            {
681                buf.append( " USAGE " ).append( UsageEnum.render( atd.getUsage() ) );
682            }
683    
684            return buf.append( render( atd.getExtensions() ) ).append( ")" );
685        }
686    
687    
688        /**
689         * Renders the schema extensions into a new StringBuffer.
690         *
691         * @param extensions the schema extensions map with key and values
692         * @return a StringBuffer with the extensions component of a syntax description
693         */
694        public static StringBuffer render( Map<String, List<String>> extensions )
695        {
696            StringBuffer buf = new StringBuffer();
697    
698            if ( extensions.isEmpty() )
699            {
700                return buf;
701            }
702    
703            for ( String key : extensions.keySet() )
704            {
705                buf.append( " " ).append( key ).append( " " );
706    
707                List<String> values = extensions.get( key );
708    
709                // For extensions without values like X-IS-HUMAN-READIBLE
710                if ( values == null || values.isEmpty() )
711                {
712                    continue;
713                }
714    
715                // For extensions with a single value we can use one qdstring like 'value'
716                if ( values.size() == 1 )
717                {
718                    buf.append( "'" ).append( values.get( 0 ) ).append( "' " );
719                    continue;
720                }
721    
722                // For extensions with several values we have to surround whitespace
723                // separated list of qdstrings like ( 'value0' 'value1' 'value2' )
724                buf.append( "( " );
725                for ( String value : values )
726                {
727                    buf.append( "'" ).append( value ).append( "' " );
728                }
729                buf.append( ")" );
730            }
731    
732            if ( buf.charAt( buf.length() - 1 ) != ' ' )
733            {
734                buf.append( " " );
735            }
736    
737            return buf;
738        }
739    
740    
741        /**
742         * Renders an matchingRule into a new StringBuffer according to the
743         * MatchingRule Description Syntax 1.3.6.1.4.1.1466.115.121.1.30. The syntax
744         * is described in detail within section 4.1.3. of LDAPBIS [<a
745         * href="http://ietf.org/internet-drafts/draft-ietf-ldapbis-models-12.txt">MODELS</a>]
746         * which is replicated here for convenience:
747         * 
748         * <pre>
749         *  4.1.3. Matching Rules
750         * 
751         *   Matching rules are used in performance of attribute value assertions,
752         *   such as in performance of a Compare operation.  They are also used in
753         *   evaluation of a Search filters, in determining which individual values
754         *   are be added or deleted during performance of a Modify operation, and
755         *   used in comparison of distinguished names.
756         * 
757         *   Each matching rule is identified by an object identifier (OID) and,
758         *   optionally, one or more short names (descriptors).
759         * 
760         *   Matching rule definitions are written according to the ABNF:
761         * 
762         *   MatchingRuleDescription = LPAREN WSP
763         *        numericoid                 ; object identifier
764         *         [ SP &quot;NAME&quot; SP qdescrs ]   ; short names (descriptors)
765         *         [ SP &quot;DESC&quot; SP qdstring ]  ; description
766         *         [ SP &quot;OBSOLETE&quot; ]          ; not active
767         *         SP &quot;SYNTAX&quot; SP numericoid  ; assertion syntax
768         *         extensions WSP RPAREN      ; extensions
769         * 
770         *   where:
771         *     &lt;numericoid&gt; is object identifier assigned to this matching rule;
772         *     NAME &lt;qdescrs&gt; are short names (descriptors) identifying this
773         *         matching rule;
774         *     DESC &lt;qdstring&gt; is a short descriptive string;
775         *     OBSOLETE indicates this matching rule is not active;
776         *     SYNTAX identifies the assertion syntax (the syntax of the assertion
777         *         value) by object identifier; and
778         *     &lt;extensions&gt; describe extensions.
779         * </pre>
780         * @param mr the MatchingRule to render the description for
781         * @return the StringBuffer containing the rendered matchingRule description
782         * @throws NamingException if there are problems accessing the objects
783         * associated with the MatchingRule.
784         */
785        public static StringBuffer render( MatchingRule mr ) throws NamingException
786        {
787            StringBuffer buf = new StringBuffer();
788            buf.append( "( " ).append( mr.getOid() );
789    
790            if ( mr.getNamesRef() != null && mr.getNamesRef().length > 0 )
791            {
792                buf.append( " NAME " );
793                render( buf, mr.getNamesRef() ).append( " " );
794            }
795            else
796            {
797                buf.append( " " );
798            }
799    
800            if ( mr.getDescription() != null )
801            {
802                buf.append( "DESC " ).append( "'" ).append( mr.getDescription() ).append( "' " );
803            }
804    
805            if ( mr.isObsolete() )
806            {
807                buf.append( " OBSOLETE" );
808            }
809    
810            if ( mr.getSyntax() != null )
811            {
812                buf.append( " SYNTAX " ).append( mr.getSyntax().getOid() );
813            }
814    
815            buf.append( " X-SCHEMA '" );
816            buf.append( mr.getSchema() );
817            buf.append( "'" );
818    
819            // @todo extensions are not presently supported and skipped
820            // the extensions would go here before closing off the description
821    
822            buf.append( " )" );
823    
824            return buf;
825        }
826    
827    
828        /**
829         * Renders a Syntax into a new StringBuffer according to the LDAP Syntax
830         * Description Syntax 1.3.6.1.4.1.1466.115.121.1.54. The syntax is described
831         * in detail within section 4.1.5. of LDAPBIS [<a
832         * href="http://ietf.org/internet-drafts/draft-ietf-ldapbis-models-12.txt">MODELS</a>]
833         * which is replicated here for convenience:
834         * 
835         * <pre>
836         *  LDAP syntax definitions are written according to the ABNF:
837         * 
838         *   SyntaxDescription = LPAREN WSP
839         *       numericoid                 ; object identifier
840         *       [ SP &quot;DESC&quot; SP qdstring ]  ; description
841         *       extensions WSP RPAREN      ; extensions
842         * 
843         *  where:
844         *   &lt;numericoid&gt; is the object identifier assigned to this LDAP syntax;
845         *   DESC &lt;qdstring&gt; is a short descriptive string; and
846         *   &lt;extensions&gt; describe extensions.
847         * </pre>
848         * @param syntax the Syntax to render the description for
849         * @return the StringBuffer containing the rendered syntax description
850         */
851        public static StringBuffer render( Syntax syntax )
852        {
853            StringBuffer buf = new StringBuffer();
854            buf.append( "( " ).append( syntax.getOid() ).append( " " );
855    
856            if ( syntax.getDescription() != null )
857            {
858                buf.append( "DESC " ).append( "'" ).append( syntax.getDescription() ).append( "' " );
859            }
860    
861            buf.append( " X-SCHEMA '" );
862            buf.append( syntax.getSchema() );
863    
864            if ( syntax.isHumanReadable() )
865            {
866                buf.append( "' X-IS-HUMAN-READABLE 'true'" );
867            }
868            else
869            {
870                buf.append( "' X-IS-HUMAN-READABLE 'false'" );
871            }
872    
873            // @todo extensions are not presently supported and skipped
874            // the extensions would go here before closing off the description
875    
876            buf.append( " )" );
877    
878            return buf;
879        }
880    
881    
882        /**
883         * NOT FULLY IMPLEMENTED!
884         */
885        public static StringBuffer render( MatchingRuleUse mru )
886        {
887            StringBuffer buf = new StringBuffer();
888            buf.append( "( " ).append( mru.getOid() ).append( " NAME " );
889            render( buf, mru.getNamesRef() ).append( " " );
890    
891            if ( mru.getDescription() != null )
892            {
893                buf.append( "DESC " ).append( "'" ).append( mru.getDescription() ).append( "' " );
894            }
895    
896            buf.append( " X-SCHEMA '" );
897            buf.append( mru.getSchema() );
898            buf.append( "'" );
899    
900            // @todo extensions are not presently supported and skipped
901            // the extensions would go here before closing off the description
902    
903            buf.append( " )" );
904    
905            return buf;
906        }
907    
908    
909        /**
910         * NOT FULLY IMPLEMENTED!
911         */
912        public static StringBuffer render( DITContentRule dcr )
913        {
914            StringBuffer buf = new StringBuffer();
915            buf.append( "( " ).append( dcr.getOid() ).append( " NAME " );
916            render( buf, dcr.getNamesRef() ).append( " " );
917    
918            if ( dcr.getDescription() != null )
919            {
920                buf.append( "DESC " ).append( "'" ).append( dcr.getDescription() ).append( "' " );
921            }
922    
923            buf.append( " X-SCHEMA '" );
924            buf.append( dcr.getSchema() );
925            buf.append( "'" );
926    
927            // @todo extensions are not presently supported and skipped
928            // the extensions would go here before closing off the description
929    
930            buf.append( " )" );
931    
932            return buf;
933        }
934    
935    
936        /**
937         * NOT FULLY IMPLEMENTED!
938         */
939        public static StringBuffer render( DITStructureRule dsr )
940        {
941            StringBuffer buf = new StringBuffer();
942            buf.append( "( " ).append( dsr.getOid() ).append( " NAME " );
943            render( buf, dsr.getNamesRef() ).append( " " );
944    
945            if ( dsr.getDescription() != null )
946            {
947                buf.append( "DESC " ).append( "'" ).append( dsr.getDescription() ).append( "' " );
948            }
949    
950            buf.append( " X-SCHEMA '" );
951            buf.append( dsr.getSchema() );
952            buf.append( "' )" );
953    
954            return buf;
955        }
956    
957    
958        /**
959         * NOT FULLY IMPLEMENTED!
960         */
961        public static StringBuffer render( NameForm nf )
962        {
963            StringBuffer buf = new StringBuffer();
964            buf.append( "( " ).append( nf.getOid() ).append( " NAME " );
965            render( buf, nf.getNamesRef() ).append( " " );
966    
967            if ( nf.getDescription() != null )
968            {
969                buf.append( "DESC " ).append( "'" ).append( nf.getDescription() ).append( "' " );
970            }
971    
972            buf.append( " X-SCHEMA '" );
973            buf.append( nf.getSchema() );
974            buf.append( "' )" );
975    
976            return buf;
977        }
978    
979    
980        /**
981         * Returns a String description of a schema. The resulting String format is :
982         * <br>
983         * (OID [DESC '<description>'] FQCN <fcqn> [BYTECODE <bytecode>] X-SCHEMA '<schema>')
984         * <br>
985         * @param description The description to transform to a String
986         * @return
987         */
988        public static String render( AbstractAdsSchemaDescription description )
989        {
990            StringBuilder buf = new StringBuilder();
991            buf.append( "( " ).append( description.getNumericOid() ).append( " " );
992    
993            if ( description.getDescription() != null )
994            {
995                buf.append( "DESC " ).append( "'" ).append( description.getDescription() ).append( "' " );
996            }
997    
998            buf.append( "FQCN " ).append( description.getFqcn() ).append( " " );
999    
1000            if ( description.getBytecode() != null )
1001            {
1002                buf.append( "BYTECODE " ).append( description.getBytecode() );
1003            }
1004    
1005            buf.append( " X-SCHEMA '" );
1006            buf.append( getSchema( description ) );
1007            buf.append( "' )" );
1008    
1009            return buf.toString();
1010        }
1011    
1012    
1013        private static String getSchema( AbstractSchemaDescription desc )
1014        {
1015            List<String> values = desc.getExtensions().get( "X-SCHEMA" );
1016    
1017            if ( values == null || values.size() == 0 )
1018            {
1019                return "other";
1020            }
1021    
1022            return values.get( 0 );
1023        }
1024    
1025    
1026        /**
1027         * Remove the options from the attributeType, and returns the ID.
1028         * 
1029         * RFC 4512 :
1030         * attributedescription = attributetype options
1031         * attributetype = oid
1032         * options = *( SEMI option )
1033         * option = 1*keychar
1034         */
1035        public static String stripOptions( String attributeId )
1036        {
1037            int optionsPos = attributeId.indexOf( ";" ); 
1038            
1039            if ( optionsPos != -1 )
1040            {
1041                return attributeId.substring( 0, optionsPos );
1042            }
1043            else
1044            {
1045                return attributeId;
1046            }
1047        }
1048        
1049        /**
1050         * Get the options from the attributeType.
1051         * 
1052         * For instance, given :
1053         * jpegphoto;binary;lang=jp
1054         * 
1055         * your get back a set containing { "binary", "lang=jp" }
1056         */
1057        public static Set<String> getOptions( String attributeId )
1058        {
1059            int optionsPos = attributeId.indexOf( ";" ); 
1060    
1061            if ( optionsPos != -1 )
1062            {
1063                Set<String> options = new HashSet<String>();
1064                
1065                String[] res = attributeId.substring( optionsPos + 1 ).split( ";" );
1066                
1067                for ( String option:res )
1068                {
1069                    if ( !StringTools.isEmpty( option ) )
1070                    {
1071                        options.add( option );
1072                    }
1073                }
1074                
1075                return options;
1076            }
1077            else
1078            {
1079                return null;
1080            }
1081        }
1082        
1083        
1084        /**
1085         * Transform an UUID in a byte array
1086         * @param uuid The UUID to transform
1087         * @return The byte[] representing the UUID
1088         */
1089        public static byte[] uuidToBytes( UUID uuid )
1090        {
1091            Long low = uuid.getLeastSignificantBits();
1092            Long high = uuid.getMostSignificantBits();
1093            byte[] bytes=new byte[16];
1094            
1095            bytes[0]  = (byte) ((high & 0xff00000000000000L)>>56);
1096            bytes[1]  = (byte) ((high & 0x00ff000000000000L)>>48);
1097            bytes[2]  = (byte) ((high & 0x0000ff0000000000L)>>40);
1098            bytes[3]  = (byte) ((high & 0x000000ff00000000L)>>32);
1099            bytes[4]  = (byte) ((high & 0x00000000ff000000L)>>24);
1100            bytes[5]  = (byte) ((high & 0x0000000000ff0000L)>>16);
1101            bytes[6]  = (byte) ((high & 0x000000000000ff00L)>>8);
1102            bytes[7]  = (byte) (high & 0x00000000000000ffL);
1103            bytes[8]  = (byte) ((low & 0xff00000000000000L)>>56);
1104            bytes[9]  = (byte) ((low & 0x00ff000000000000L)>>48);
1105            bytes[10] = (byte) ((low & 0x0000ff0000000000L)>>40);
1106            bytes[11] = (byte) ((low & 0x000000ff00000000L)>>32);
1107            bytes[12] = (byte) ((low & 0x00000000ff000000L)>>24);
1108            bytes[13] = (byte) ((low & 0x0000000000ff0000L)>>16);
1109            bytes[14] = (byte) ((low & 0x000000000000ff00L)>>8);
1110            bytes[15] = (byte) (low & 0x00000000000000ffL);
1111            
1112            return bytes;
1113        }
1114    }