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 */
020package org.apache.directory.api.ldap.model.schema;
021
022
023import java.util.HashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import java.util.UUID;
028
029import org.apache.directory.api.i18n.I18n;
030import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
031import org.apache.directory.api.ldap.model.entry.Attribute;
032import org.apache.directory.api.ldap.model.entry.Entry;
033import org.apache.directory.api.ldap.model.entry.Modification;
034import org.apache.directory.api.ldap.model.entry.Value;
035import org.apache.directory.api.ldap.model.exception.LdapException;
036import org.apache.directory.api.util.Strings;
037
038
039/**
040 * Various utility methods for schema functions and objects.
041 * 
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 */
044public final class SchemaUtils
045{
046    /**
047     * Private constructor.
048     */
049    private SchemaUtils()
050    {
051    }
052
053
054    /**
055     * Gets the target entry as it would look after a modification operation
056     * were performed on it.
057     * 
058     * @param mods the modifications performed on the entry
059     * @param entry the source entry that is modified
060     * @return the resultant entry after the modifications have taken place
061     * @throws LdapException if there are problems accessing attributes
062     */
063    public static Entry getTargetEntry( List<? extends Modification> mods, Entry entry )
064        throws LdapException
065    {
066        Entry targetEntry = entry.clone();
067
068        for ( Modification mod : mods )
069        {
070            String id = mod.getAttribute().getId();
071
072            switch ( mod.getOperation() )
073            {
074                case REPLACE_ATTRIBUTE:
075                    targetEntry.put( mod.getAttribute() );
076                    break;
077
078                case ADD_ATTRIBUTE:
079                    Attribute combined = mod.getAttribute().clone();
080                    Attribute toBeAdded = mod.getAttribute();
081                    Attribute existing = entry.get( id );
082
083                    if ( existing != null )
084                    {
085                        for ( Value<?> value : existing )
086                        {
087                            combined.add( value );
088                        }
089                    }
090
091                    for ( Value<?> value : toBeAdded )
092                    {
093                        combined.add( value );
094                    }
095
096                    targetEntry.put( combined );
097                    break;
098
099                case REMOVE_ATTRIBUTE:
100                    Attribute toBeRemoved = mod.getAttribute();
101
102                    if ( toBeRemoved.size() == 0 )
103                    {
104                        targetEntry.removeAttributes( id );
105                    }
106                    else
107                    {
108                        existing = targetEntry.get( id );
109
110                        if ( existing != null )
111                        {
112                            for ( Value<?> value : toBeRemoved )
113                            {
114                                existing.remove( value );
115                            }
116                        }
117                    }
118
119                    break;
120
121                default:
122                    throw new IllegalStateException( I18n.err( I18n.ERR_04328, mod.getOperation() ) );
123            }
124        }
125
126        return targetEntry;
127    }
128
129
130    // ------------------------------------------------------------------------
131    // qdescrs rendering operations
132    // ------------------------------------------------------------------------
133
134    /**
135     * Renders qdescrs into an existing buffer.
136     * 
137     * @param buf
138     *            the string buffer to render the quoted description strs into
139     * @param qdescrs
140     *            the quoted description strings to render
141     * @return the same string buffer that was given for call chaining
142     */
143    public static StringBuffer render( StringBuffer buf, List<String> qdescrs )
144    {
145        if ( ( qdescrs == null ) || ( qdescrs.size() == 0 ) )
146        {
147            return buf;
148        }
149        else if ( qdescrs.size() == 1 )
150        {
151            buf.append( "'" ).append( qdescrs.get( 0 ) ).append( "'" );
152        }
153        else
154        {
155            buf.append( "( " );
156
157            for ( String qdescr : qdescrs )
158            {
159                buf.append( "'" ).append( qdescr ).append( "' " );
160            }
161
162            buf.append( ")" );
163        }
164
165        return buf;
166    }
167
168
169    /**
170     * Renders qdescrs into a new buffer.<br>
171     * <pre>
172     * descrs ::= qdescr | '(' WSP qdescrlist WSP ')'
173     * qdescrlist ::= [ qdescr ( SP qdescr )* ]
174     * qdescr     ::= SQUOTE descr SQUOTE
175     * </pre>
176     * @param qdescrs the quoted description strings to render
177     * @return the string buffer the qdescrs are rendered into
178     */
179    /* No qualifier */static StringBuffer renderQDescrs( StringBuffer buf, List<String> qdescrs )
180    {
181        if ( ( qdescrs == null ) || ( qdescrs.size() == 0 ) )
182        {
183            return buf;
184        }
185
186        if ( qdescrs.size() == 1 )
187        {
188            buf.append( '\'' ).append( qdescrs.get( 0 ) ).append( '\'' );
189        }
190        else
191        {
192            buf.append( "( " );
193
194            for ( String qdescr : qdescrs )
195            {
196                buf.append( '\'' ).append( qdescr ).append( "' " );
197            }
198
199            buf.append( ")" );
200        }
201
202        return buf;
203    }
204
205
206    /**
207     * Renders QDString into a new buffer.<br>
208     * 
209     * @param qdescrs the quoted description strings to render
210     * @return the string buffer the qdescrs are rendered into
211     */
212    private static StringBuffer renderQDString( StringBuffer buf, String qdString )
213    {
214        buf.append( '\'' );
215
216        for ( char c : qdString.toCharArray() )
217        {
218            switch ( c )
219            {
220                case 0x27:
221                    buf.append( "\\27" );
222                    break;
223
224                case 0x5C:
225                    buf.append( "\\5C" );
226                    break;
227
228                default:
229                    buf.append( c );
230                    break;
231            }
232        }
233
234        buf.append( '\'' );
235
236        return buf;
237    }
238
239
240    // ------------------------------------------------------------------------
241    // objectClass list rendering operations
242    // ------------------------------------------------------------------------
243
244    /**
245     * Renders a list of object classes for things like a list of superior
246     * objectClasses using the ( oid $ oid ) format.
247     * 
248     * @param ocs
249     *            the objectClasses to list
250     * @return a buffer which contains the rendered list
251     */
252    public static StringBuffer render( ObjectClass[] ocs )
253    {
254        StringBuffer buf = new StringBuffer();
255
256        return render( buf, ocs );
257    }
258
259
260    /**
261     * Renders a list of object classes for things like a list of superior
262     * objectClasses using the ( oid $ oid ) format into an existing buffer.
263     * 
264     * @param buf
265     *            the string buffer to render the list of objectClasses into
266     * @param ocs
267     *            the objectClasses to list
268     * @return a buffer which contains the rendered list
269     */
270    public static StringBuffer render( StringBuffer buf, ObjectClass[] ocs )
271    {
272        if ( ocs == null || ocs.length == 0 )
273        {
274            return buf;
275        }
276        else if ( ocs.length == 1 )
277        {
278            buf.append( ocs[0].getName() );
279        }
280        else
281        {
282            buf.append( "( " );
283
284            for ( int ii = 0; ii < ocs.length; ii++ )
285            {
286                if ( ii + 1 < ocs.length )
287                {
288                    buf.append( ocs[ii].getName() ).append( " $ " );
289                }
290                else
291                {
292                    buf.append( ocs[ii].getName() );
293                }
294            }
295
296            buf.append( " )" );
297        }
298
299        return buf;
300    }
301
302
303    // ------------------------------------------------------------------------
304    // attributeType list rendering operations
305    // ------------------------------------------------------------------------
306
307    /**
308     * Renders a list of attributeTypes for things like the must or may list of
309     * objectClasses using the ( oid $ oid ) format.
310     * 
311     * @param ats
312     *            the attributeTypes to list
313     * @return a buffer which contains the rendered list
314     */
315    public static StringBuffer render( AttributeType[] ats )
316    {
317        StringBuffer buf = new StringBuffer();
318        return render( buf, ats );
319    }
320
321
322    /**
323     * Renders a list of attributeTypes for things like the must or may list of
324     * objectClasses using the ( oid $ oid ) format into an existing buffer.
325     * 
326     * @param buf
327     *            the string buffer to render the list of attributeTypes into
328     * @param ats
329     *            the attributeTypes to list
330     * @return a buffer which contains the rendered list
331     */
332    public static StringBuffer render( StringBuffer buf, AttributeType[] ats )
333    {
334        if ( ats == null || ats.length == 0 )
335        {
336            return buf;
337        }
338        else if ( ats.length == 1 )
339        {
340            buf.append( ats[0].getName() );
341        }
342        else
343        {
344            buf.append( "( " );
345            for ( int ii = 0; ii < ats.length; ii++ )
346            {
347                if ( ii + 1 < ats.length )
348                {
349                    buf.append( ats[ii].getName() ).append( " $ " );
350                }
351                else
352                {
353                    buf.append( ats[ii].getName() );
354                }
355            }
356            buf.append( " )" );
357        }
358
359        return buf;
360    }
361
362
363    // ------------------------------------------------------------------------
364    // schema object rendering operations
365    // ------------------------------------------------------------------------
366
367
368    /**
369     * Renders the schema extensions into a new StringBuffer.
370     *
371     * @param extensions the schema extensions map with key and values
372     * @return a StringBuffer with the extensions component of a syntax description
373     */
374    public static StringBuffer render( Map<String, List<String>> extensions )
375    {
376        StringBuffer buf = new StringBuffer();
377
378        if ( extensions.isEmpty() )
379        {
380            return buf;
381        }
382
383        for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
384        {
385            buf.append( " " ).append( entry.getKey() ).append( " " );
386
387            List<String> values = entry.getValue();
388
389            // For extensions without values like X-IS-HUMAN-READIBLE
390            if ( values == null || values.isEmpty() )
391            {
392                continue;
393            }
394
395            // For extensions with a single value we can use one qdstring like 'value'
396            if ( values.size() == 1 )
397            {
398                buf.append( "'" ).append( values.get( 0 ) ).append( "' " );
399                continue;
400            }
401
402            // For extensions with several values we have to surround whitespace
403            // separated list of qdstrings like ( 'value0' 'value1' 'value2' )
404            buf.append( "( " );
405            for ( String value : values )
406            {
407                buf.append( "'" ).append( value ).append( "' " );
408            }
409            buf.append( ")" );
410        }
411
412        if ( buf.charAt( buf.length() - 1 ) != ' ' )
413        {
414            buf.append( " " );
415        }
416
417        return buf;
418    }
419
420
421    
422
423    /**
424     * Returns a String description of a schema. The resulting String format is :
425     * <br>
426     * (OID [DESC '<description>'] FQCN <fcqn> [BYTECODE <bytecode>] X-SCHEMA '<schema>')
427     * <br>
428     * @param description The description to transform to a String
429     * @return
430     */
431    public static String render( LoadableSchemaObject description )
432    {
433        StringBuffer buf = new StringBuffer();
434        buf.append( "( " ).append( description.getOid() );
435
436        if ( description.getDescription() != null )
437        {
438            buf.append( " DESC " );
439            renderQDString( buf, description.getDescription() );
440        }
441
442        buf.append( " FQCN " ).append( description.getFqcn() );
443
444        if ( !Strings.isEmpty( description.getBytecode() ) )
445        {
446            buf.append( " BYTECODE " ).append( description.getBytecode() );
447        }
448
449        buf.append( " X-SCHEMA '" );
450        buf.append( getSchemaName( description ) );
451        buf.append( "' )" );
452
453        return buf.toString();
454    }
455
456
457    private static String getSchemaName( SchemaObject desc )
458    {
459        List<String> values = desc.getExtensions().get( MetaSchemaConstants.X_SCHEMA_AT );
460
461        if ( values == null || values.size() == 0 )
462        {
463            return MetaSchemaConstants.SCHEMA_OTHER;
464        }
465
466        return values.get( 0 );
467    }
468
469
470    /**
471     * Remove the options from the attributeType, and returns the ID.
472     * 
473     * RFC 4512 :
474     * attributedescription = attributetype options
475     * attributetype = oid
476     * options = *( SEMI option )
477     * option = 1*keychar
478     */
479    public static String stripOptions( String attributeId )
480    {
481        int optionsPos = attributeId.indexOf( ';' );
482
483        if ( optionsPos != -1 )
484        {
485            return attributeId.substring( 0, optionsPos );
486        }
487        else
488        {
489            return attributeId;
490        }
491    }
492
493
494    /**
495     * Get the options from the attributeType.
496     * 
497     * For instance, given :
498     * jpegphoto;binary;lang=jp
499     * 
500     * your get back a set containing { "binary", "lang=jp" }
501     */
502    public static Set<String> getOptions( String attributeId )
503    {
504        int optionsPos = attributeId.indexOf( ';' );
505
506        if ( optionsPos != -1 )
507        {
508            Set<String> options = new HashSet<String>();
509
510            String[] res = attributeId.substring( optionsPos + 1 ).split( ";" );
511
512            for ( String option : res )
513            {
514                if ( !Strings.isEmpty( option ) )
515                {
516                    options.add( option );
517                }
518            }
519
520            return options;
521        }
522        else
523        {
524            return null;
525        }
526    }
527
528
529    /**
530     * Transform an UUID in a byte array
531     * @param uuid The UUID to transform
532     * @return The byte[] representing the UUID
533     */
534    public static byte[] uuidToBytes( UUID uuid )
535    {
536        Long low = uuid.getLeastSignificantBits();
537        Long high = uuid.getMostSignificantBits();
538        byte[] bytes = new byte[16];
539
540        bytes[0] = ( byte ) ( ( high & 0xff00000000000000L ) >> 56 );
541        bytes[1] = ( byte ) ( ( high & 0x00ff000000000000L ) >> 48 );
542        bytes[2] = ( byte ) ( ( high & 0x0000ff0000000000L ) >> 40 );
543        bytes[3] = ( byte ) ( ( high & 0x000000ff00000000L ) >> 32 );
544        bytes[4] = ( byte ) ( ( high & 0x00000000ff000000L ) >> 24 );
545        bytes[5] = ( byte ) ( ( high & 0x0000000000ff0000L ) >> 16 );
546        bytes[6] = ( byte ) ( ( high & 0x000000000000ff00L ) >> 8 );
547        bytes[7] = ( byte ) ( high & 0x00000000000000ffL );
548        bytes[8] = ( byte ) ( ( low & 0xff00000000000000L ) >> 56 );
549        bytes[9] = ( byte ) ( ( low & 0x00ff000000000000L ) >> 48 );
550        bytes[10] = ( byte ) ( ( low & 0x0000ff0000000000L ) >> 40 );
551        bytes[11] = ( byte ) ( ( low & 0x000000ff00000000L ) >> 32 );
552        bytes[12] = ( byte ) ( ( low & 0x00000000ff000000L ) >> 24 );
553        bytes[13] = ( byte ) ( ( low & 0x0000000000ff0000L ) >> 16 );
554        bytes[14] = ( byte ) ( ( low & 0x000000000000ff00L ) >> 8 );
555        bytes[15] = ( byte ) ( low & 0x00000000000000ffL );
556
557        return bytes;
558    }
559}