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