View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   * 
19   */
20  package org.apache.directory.api.ldap.model.schema;
21  
22  
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.UUID;
28  
29  import org.apache.directory.api.i18n.I18n;
30  import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
31  import org.apache.directory.api.ldap.model.entry.Attribute;
32  import org.apache.directory.api.ldap.model.entry.Entry;
33  import org.apache.directory.api.ldap.model.entry.Modification;
34  import org.apache.directory.api.ldap.model.entry.Value;
35  import org.apache.directory.api.ldap.model.exception.LdapException;
36  import org.apache.directory.api.util.Strings;
37  
38  
39  /**
40   * Various utility methods for schema functions and objects.
41   * 
42   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
43   */
44  public final class SchemaUtils
45  {
46      /**
47       * Private constructor.
48       */
49      private SchemaUtils()
50      {
51      }
52  
53  
54      /**
55       * Gets the target entry as it would look after a modification operation
56       * were performed on it.
57       * 
58       * @param mods the modifications performed on the entry
59       * @param entry the source entry that is modified
60       * @return the resultant entry after the modifications have taken place
61       * @throws LdapException if there are problems accessing attributes
62       */
63      public static Entry getTargetEntry( List<? extends Modification> mods, Entry entry )
64          throws LdapException
65      {
66          Entry targetEntry = entry.clone();
67  
68          for ( Modification mod : mods )
69          {
70              String id = mod.getAttribute().getId();
71  
72              switch ( mod.getOperation() )
73              {
74                  case REPLACE_ATTRIBUTE:
75                      targetEntry.put( mod.getAttribute() );
76                      break;
77  
78                  case ADD_ATTRIBUTE:
79                      Attribute combined = mod.getAttribute().clone();
80                      Attribute toBeAdded = mod.getAttribute();
81                      Attribute existing = entry.get( id );
82  
83                      if ( existing != null )
84                      {
85                          for ( Value<?> value : existing )
86                          {
87                              combined.add( value );
88                          }
89                      }
90  
91                      for ( Value<?> value : toBeAdded )
92                      {
93                          combined.add( value );
94                      }
95  
96                      targetEntry.put( combined );
97                      break;
98  
99                  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 }