001    /*
002     * Copyright 2009-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2016 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.util.UUID;
026    
027    import com.unboundid.ldap.sdk.DN;
028    import com.unboundid.ldap.sdk.DNEntrySource;
029    import com.unboundid.ldap.sdk.Entry;
030    import com.unboundid.ldap.sdk.LDAPInterface;
031    import com.unboundid.ldap.sdk.LDAPException;
032    import com.unboundid.util.ThreadSafety;
033    import com.unboundid.util.ThreadSafetyLevel;
034    
035    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
036    import static com.unboundid.util.StaticUtils.*;
037    import static com.unboundid.util.Validator.*;
038    
039    
040    
041    /**
042     * This class provides a set of utilities that may be used in the course of
043     * persistence processing.
044     */
045    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
046    public final class PersistUtils
047    {
048      /**
049       * Prevent this utility class from being instantiated.
050       */
051      private PersistUtils()
052      {
053        // No implementation required.
054      }
055    
056    
057    
058      /**
059       * Indicates whether the provided string could be used as a valid attribute or
060       * object class name.  Numeric OIDs will also be considered acceptable.
061       *
062       * @param  s  The string for which to make the determination.
063       * @param  r  A buffer to which the unacceptable reason may be appended.  It
064       *            must not be {@code null}.
065       *
066       * @return  {@code true} if the provided string is acceptable for use as an
067       *          LDAP attribute or object class name, or {@code false} if not.
068       */
069      public static boolean isValidLDAPName(final String s, final StringBuilder r)
070      {
071        return isValidLDAPName(s, false, r);
072      }
073    
074    
075    
076      /**
077       * Indicates whether the provided string could be used as a valid attribute or
078       * object class name.  Numeric OIDs will also be considered acceptable.
079       *
080       * @param  s  The string for which to make the determination.
081       * @param  o  Indicates whether the name should be allowed to contain
082       *            attribute options (e.g., a semicolon with one or more valid
083       *            characters after it).
084       * @param  r  A buffer to which the unacceptable reason may be appended.  It
085       *            must not be {@code null}.
086       *
087       * @return  {@code true} if the provided string is acceptable for use as an
088       *          LDAP attribute or object class name, or {@code false} if not.
089       */
090      public static boolean isValidLDAPName(final String s, final boolean o,
091                                            final StringBuilder r)
092      {
093        int length;
094        if ((s == null) || ((length = s.length()) == 0))
095        {
096          r.append(ERR_LDAP_NAME_VALIDATOR_EMPTY.get());
097          return false;
098        }
099    
100        final String baseName;
101        final int semicolonPos = s.indexOf(';');
102        if (semicolonPos > 0)
103        {
104          if (! o)
105          {
106            r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_CHAR.get(s, ';',
107                 semicolonPos));
108            return false;
109          }
110    
111          baseName = s.substring(0, semicolonPos);
112          length = baseName.length();
113    
114          final String optionsStr = s.substring(semicolonPos+1);
115          if (! isValidOptionSet(baseName, optionsStr, r))
116          {
117            return false;
118          }
119        }
120        else
121        {
122          baseName = s;
123        }
124    
125        if (isNumericOID(baseName))
126        {
127          return true;
128        }
129    
130        for (int i=0; i < length; i++)
131        {
132          final char c = baseName.charAt(i);
133          if (((c >= 'a') && (c <= 'z')) ||
134              ((c >= 'A') && (c <= 'Z')))
135          {
136            // This will always be acceptable.
137          }
138          else if (((c >= '0') && (c <= '9')) || (c == '-'))
139          {
140            // This will be acceptable for all but the first character.
141            if (i == 0)
142            {
143              r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_FIRST_CHAR.get(s));
144              return false;
145            }
146          }
147          else
148          {
149            r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_CHAR.get(s, c, i));
150            return false;
151          }
152        }
153    
154        return true;
155      }
156    
157    
158    
159      /**
160       * Indicates whether the provided string represents a valid set of attribute
161       * options.  It should not contain the initial semicolon.
162       *
163       * @param  b  The base name for the attribute, without the option string or
164       *            the semicolon used to delimit the option string from the base
165       *            name.
166       * @param  o  The option string to examine.  It must not be {@code null}, and
167       *            must not contain the initial semicolon.
168       * @param  r  A buffer to which the unacceptable reason may be appended.  It
169       *            must not be {@code null}.
170       *
171       * @return  {@code true} if the provided string represents a valid set of
172       *          options, or {@code false} if not.
173       */
174      private static boolean isValidOptionSet(final String b, final String o,
175                                              final StringBuilder r)
176      {
177        boolean lastWasSemicolon = true;
178    
179        for (int i=0; i < o.length(); i++)
180        {
181          final char c = o.charAt(i);
182          if (c == ';')
183          {
184            if (lastWasSemicolon)
185            {
186              r.append(
187                   ERR_LDAP_NAME_VALIDATOR_OPTION_WITH_CONSECUTIVE_SEMICOLONS.get(
188                        b + ';' + o));
189              return false;
190            }
191            else
192            {
193              lastWasSemicolon = true;
194            }
195          }
196          else
197          {
198            lastWasSemicolon = false;
199            if (((c >= 'a') && (c <= 'z')) ||
200                ((c >= 'A') && (c <= 'Z')) ||
201                ((c >= '0') && (c <= '9')) ||
202                (c == '-'))
203            {
204              // This will always be acceptable.
205            }
206            else
207            {
208              r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_OPTION_CHAR.get(
209                   (b + ';' + o), c, (b.length() + 1 + i)));
210              return false;
211            }
212          }
213        }
214    
215        if (lastWasSemicolon)
216        {
217          r.append(ERR_LDAP_NAME_VALIDATOR_ENDS_WITH_SEMICOLON.get(b + ';' + o));
218          return false;
219        }
220    
221        return true;
222      }
223    
224    
225    
226      /**
227       * Indicates whether the provided string could be used as a valid Java
228       * identifier.  The identifier must begin with an ASCII letter or underscore,
229       * and must contain only ASCII letters, ASCII digits, and the underscore
230       * character.  Even though a dollar sign is technically allowed, it will not
231       * be considered valid for the purpose of this method.  Similarly, even though
232       * Java keywords are not allowed, they will not be rejected by this method.
233       *
234       * @param  s  The string for which to make the determination.  It must not be
235       *            {@code null}.
236       * @param  r  A buffer to which the unacceptable reason may be appended.  It
237       *            must not be {@code null}.
238       *
239       * @return  {@code true} if the provided string is acceptable for use as a
240       *          Java identifier, or {@code false} if not.
241       */
242      public static boolean isValidJavaIdentifier(final String s,
243                                                  final StringBuilder r)
244      {
245        final int length = s.length();
246        for (int i=0; i < length; i++)
247        {
248          final char c = s.charAt(i);
249          if (((c >= 'a') && (c <= 'z')) ||
250              ((c >= 'A') && (c <= 'Z')) ||
251              (c == '_'))
252          {
253            // This will always be acceptable.
254          }
255          else if ((c >= '0') && (c <= '9'))
256          {
257            if (i == 0)
258            {
259              r.append(ERR_JAVA_NAME_VALIDATOR_INVALID_FIRST_CHAR_DIGIT.get(s));
260              return false;
261            }
262          }
263          else
264          {
265            r.append(ERR_JAVA_NAME_VALIDATOR_INVALID_CHAR.get(s, c, i));
266            return false;
267          }
268        }
269    
270        return true;
271      }
272    
273    
274    
275      /**
276       * Transforms the provided string if necessary so that it may be used as a
277       * valid Java identifier.  If the provided string is already a valid Java
278       * identifier, then it will be returned as-is.  Otherwise, it will be
279       * transformed to make it more suitable.
280       *
281       * @param  s  The attribute or object class name to be converted to a Java
282       *            identifier.
283       *
284       * @return  A string that may be used as a valid Java identifier.
285       */
286      public static String toJavaIdentifier(final String s)
287      {
288        final int length;
289        if ((s == null) || ((length = s.length()) == 0))
290        {
291          // This will be ugly, but safe.
292          return toJavaIdentifier(UUID.randomUUID().toString());
293        }
294    
295        boolean nextUpper = false;
296        final StringBuilder b = new StringBuilder(length);
297        for (int i=0; i < length; i++)
298        {
299          final char c = s.charAt(i);
300          if (((c >= 'a') && (c <= 'z')) ||
301              ((c >= 'A') && (c <= 'Z')))
302          {
303            if (nextUpper)
304            {
305              b.append(Character.toUpperCase(c));
306            }
307            else
308            {
309              b.append(c);
310            }
311    
312            nextUpper = false;
313          }
314          else if ((c >= '0') && (c <= '9'))
315          {
316            if (i == 0)
317            {
318              // Java identifiers can't begin with a digit, but they can begin with
319              // an underscore followed by a digit, so we'll use that instead.
320              b.append('_');
321            }
322    
323            b.append(c);
324            nextUpper = false;
325          }
326          else
327          {
328            // If the provided string was a valid LDAP attribute or object class
329            // name, then this should be a dash, but we'll be safe and take the same
330            // action for any remaining character.
331            nextUpper = true;
332          }
333        }
334    
335        if (b.length() == 0)
336        {
337          // This should only happen if the provided string wasn't a valid LDAP
338          // attribute or object class name to start with.
339          return toJavaIdentifier(UUID.randomUUID().toString());
340        }
341    
342        return b.toString();
343      }
344    
345    
346    
347      /**
348       * Retrieves the entry with the specified DN and decodes it as an object of
349       * the specified type.
350       *
351       * @param  <T>  The type of object as which to decode the entry.
352       *
353       * @param  dn    The DN of the entry to retrieve.  It must not be
354       *               {@code null}.
355       * @param  type  The type of object as which the entry should be decoded.  It
356       *               must not be {@code null}, and the class must be marked with
357       *               the {@link LDAPObject} annotation type.
358       * @param  conn  The connection that should be used to retrieve the entry.  It
359       *               must not be {@code null}.
360       *
361       * @return  The object decoded from the specified entry, or {@code null} if
362       *          the entry cannot be retrieved (e.g., because it does not exist or
363       *          is not readable by the authenticated user).
364       *
365       * @throws  LDAPException  If a problem occurs while trying to retrieve the
366       *                         entry or decode it as the specified type of object.
367       */
368      public static <T> T getEntryAsObject(final DN dn, final Class<T> type,
369                                           final LDAPInterface conn)
370             throws LDAPException
371      {
372        ensureNotNull(dn, type, conn);
373    
374        final LDAPPersister<T> p = LDAPPersister.getInstance(type);
375    
376        final Entry e = conn.getEntry(dn.toString(),
377             p.getObjectHandler().getAttributesToRequest());
378        if (e == null)
379        {
380          return null;
381        }
382    
383        return p.decode(e);
384      }
385    
386    
387    
388      /**
389       * Retrieves and decodes the indicated entries as objects of the specified
390       * type.
391       *
392       * @param  <T>  The type of object as which to decode the entries.
393       *
394       * @param  dns   The DNs of the entries to retrieve.  It must not be
395       *               {@code null}.
396       * @param  type  The type of object as which the entries should be decoded.
397       *               It must not be {@code null}, and the class must be marked
398       *               with the {@link LDAPObject} annotation type.
399       * @param  conn  The connection that should be used to retrieve the entries.
400       *               It must not be {@code null}.
401       *
402       * @return  A {@code PersistedObjects} result that may be used to access the
403       *          objects decoded from the provided set of DNs.
404       *
405       * @throws  LDAPPersistException  If the requested type cannot be used with
406       *                                the LDAP SDK persistence framework.
407       */
408      public static <T> PersistedObjects<T> getEntriesAsObjects(final DN[] dns,
409                                                 final Class<T> type,
410                                                 final LDAPInterface conn)
411             throws LDAPPersistException
412      {
413        ensureNotNull(dns, type, conn);
414    
415        final LDAPPersister<T> p = LDAPPersister.getInstance(type);
416    
417        final DNEntrySource entrySource = new DNEntrySource(conn, dns,
418             p.getObjectHandler().getAttributesToRequest());
419        return new PersistedObjects<T>(p, entrySource);
420      }
421    }