001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2024 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-2024 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.io.Serializable;
041import java.lang.reflect.Method;
042import java.lang.reflect.Modifier;
043import java.lang.reflect.Type;
044
045import com.unboundid.ldap.sdk.Attribute;
046import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
047import com.unboundid.util.Debug;
048import com.unboundid.util.NotMutable;
049import com.unboundid.util.NotNull;
050import com.unboundid.util.Nullable;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
057
058
059
060/**
061 * This class provides a data structure that holds information about an
062 * annotated getter method.
063 */
064@NotMutable()
065@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
066public final class GetterInfo
067       implements Serializable
068{
069  /**
070   * The serial version UID for this serializable class.
071   */
072  private static final long serialVersionUID = 1578187843924054389L;
073
074
075
076  // Indicates whether the associated method value should be included in the
077  // entry created for an add operation.
078  private final boolean includeInAdd;
079
080  // Indicates whether the associated method value should be considered for
081  // inclusion in the set of modifications used for modify operations.
082  private final boolean includeInModify;
083
084  // Indicates whether the associated method value is part of the RDN.
085  private final boolean includeInRDN;
086
087  // The class that contains the associated method.
088  @NotNull private final Class<?> containingClass;
089
090  // The filter usage for the associated method.
091  @NotNull private final FilterUsage filterUsage;
092
093  // The method with which this object is associated.
094  @NotNull private final Method method;
095
096  // The encoder used for this method.
097  @NotNull private final ObjectEncoder encoder;
098
099  // The name of the associated attribute type.
100  @NotNull private final String attributeName;
101
102  // The names of the object classes for the associated attribute.
103  @NotNull private final String[] objectClasses;
104
105
106
107  /**
108   * Creates a new getter info object from the provided method.
109   *
110   * @param  m  The method to use to create this object.
111   * @param  c  The class which holds the method.
112   *
113   * @throws  LDAPPersistException  If a problem occurs while processing the
114   *                                given method.
115   */
116  GetterInfo(@NotNull final Method m, @NotNull final Class<?> c)
117       throws LDAPPersistException
118  {
119    Validator.ensureNotNull(m, c);
120
121    method = m;
122    m.setAccessible(true);
123
124    final LDAPGetter  a = m.getAnnotation(LDAPGetter.class);
125    if (a == null)
126    {
127      throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_NOT_ANNOTATED.get(
128           m.getName(), c.getName()));
129    }
130
131    final LDAPObject o = c.getAnnotation(LDAPObject.class);
132    if (o == null)
133    {
134      throw new LDAPPersistException(ERR_GETTER_INFO_CLASS_NOT_ANNOTATED.get(
135           c.getName()));
136    }
137
138    containingClass = c;
139    includeInRDN    = a.inRDN();
140    includeInAdd    = (includeInRDN || a.inAdd());
141    includeInModify = ((! includeInRDN) && a.inModify());
142    filterUsage     = a.filterUsage();
143
144    final int modifiers = m.getModifiers();
145    if (Modifier.isStatic(modifiers))
146    {
147      throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_STATIC.get(
148           m.getName(), c.getName()));
149    }
150
151    final Type[] params = m.getGenericParameterTypes();
152    if (params.length > 0)
153    {
154      throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_TAKES_ARGUMENTS.get(
155           m.getName(), c.getName()));
156    }
157
158    try
159    {
160      encoder = a.encoderClass().newInstance();
161    }
162    catch (final Exception e)
163    {
164      Debug.debugException(e);
165      throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_GET_ENCODER.get(
166           a.encoderClass().getName(), m.getName(), c.getName(),
167           StaticUtils.getExceptionMessage(e)), e);
168    }
169
170    if (! encoder.supportsType(m.getGenericReturnType()))
171    {
172      throw new LDAPPersistException(
173           ERR_GETTER_INFO_ENCODER_UNSUPPORTED_TYPE.get(
174                encoder.getClass().getName(), m.getName(), c.getName(),
175                String.valueOf(m.getGenericReturnType())));
176    }
177
178    final String structuralClass;
179    if (o.structuralClass().isEmpty())
180    {
181      structuralClass = StaticUtils.getUnqualifiedClassName(c);
182    }
183    else
184    {
185      structuralClass = o.structuralClass();
186    }
187
188    final String[] ocs = a.objectClass();
189    if ((ocs == null) || (ocs.length == 0))
190    {
191      objectClasses = new String[] { structuralClass };
192    }
193    else
194    {
195      objectClasses = ocs;
196    }
197
198    for (final String s : objectClasses)
199    {
200      if (! s.equalsIgnoreCase(structuralClass))
201      {
202        boolean found = false;
203        for (final String oc : o.auxiliaryClass())
204        {
205          if (s.equalsIgnoreCase(oc))
206          {
207            found = true;
208            break;
209          }
210        }
211
212        if (! found)
213        {
214          throw new LDAPPersistException(ERR_GETTER_INFO_INVALID_OC.get(
215               m.getName(), c.getName(), s));
216        }
217      }
218    }
219
220    final String attrName = a.attribute();
221    if ((attrName == null) || attrName.isEmpty())
222    {
223      final String methodName = m.getName();
224      if (methodName.startsWith("get") && (methodName.length() >= 4))
225      {
226        attributeName = StaticUtils.toInitialLowerCase(methodName.substring(3));
227      }
228      else
229      {
230        throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_INFER_ATTR.get(
231             methodName, c.getName()));
232      }
233    }
234    else
235    {
236      attributeName = attrName;
237    }
238  }
239
240
241
242  /**
243   * Retrieves the method with which this object is associated.
244   *
245   * @return  The method with which this object is associated.
246   */
247  @NotNull()
248  public Method getMethod()
249  {
250    return method;
251  }
252
253
254
255  /**
256   * Retrieves the class that is marked with the {@link LDAPObject} annotation
257   * and contains the associated field.
258   *
259   * @return  The class that contains the associated field.
260   */
261  @NotNull()
262  public Class<?> getContainingClass()
263  {
264    return containingClass;
265  }
266
267
268
269  /**
270   * Indicates whether the associated method value should be included in entries
271   * generated for add operations.  Note that the value returned from this
272   * method may be {@code true} even when the annotation has a value of
273   * {@code false} if the associated field is to be included in entry RDNs.
274   *
275   * @return  {@code true} if the associated method value should be included in
276   *          entries generated for add operations, or {@code false} if not.
277   */
278  public boolean includeInAdd()
279  {
280    return includeInAdd;
281  }
282
283
284
285  /**
286   * Indicates whether the associated method value should be considered for
287   * inclusion in the set of modifications generated for modify operations.
288   * Note that the value returned from this method may be {@code false} even
289   * when the annotation have a value of {@code true} if the associated field is
290   * to be included in entry RDNs.
291   *
292   * @return  {@code true} if the associated method value should be considered
293   *          for inclusion in the set of modifications generated for modify
294   *          operations, or {@code false} if not.
295   */
296  public boolean includeInModify()
297  {
298    return includeInModify;
299  }
300
301
302
303  /**
304   * Indicates whether the associated method value should be used to generate
305   * entry RDNs.
306   *
307   * @return  {@code true} if the associated method value should be used to
308   *          generate entry RDNs, or {@code false} if not.
309   */
310  public boolean includeInRDN()
311  {
312    return includeInRDN;
313  }
314
315
316
317  /**
318   * Retrieves the filter usage for the associated method.
319   *
320   * @return  The filter usage for the associated method.
321   */
322  @NotNull()
323  public FilterUsage getFilterUsage()
324  {
325    return filterUsage;
326  }
327
328
329
330  /**
331   * Retrieves the encoder that should be used for the associated method.
332   *
333   * @return  The encoder that should be used for the associated method.
334   */
335  @NotNull()
336  public ObjectEncoder getEncoder()
337  {
338    return encoder;
339  }
340
341
342
343  /**
344   * Retrieves the name of the LDAP attribute used to hold values for the
345   * associated method.
346   *
347   * @return  The name of the LDAP attribute used to hold values for the
348   *          associated method.
349   */
350  @NotNull()
351  public String getAttributeName()
352  {
353    return attributeName;
354  }
355
356
357
358  /**
359   * Retrieves the names of the object classes containing the associated
360   * attribute.
361   *
362   * @return  The names of the object classes containing the associated
363   *          attribute.
364   */
365  @NotNull()
366  public String[] getObjectClasses()
367  {
368    return objectClasses;
369  }
370
371
372
373  /**
374   * Constructs a definition for an LDAP attribute type which may be added to
375   * the directory server schema to allow it to hold the value of the associated
376   * method.  Note that the object identifier used for the constructed attribute
377   * type definition is not required to be valid or unique.
378   *
379   * @return  The constructed attribute type definition.
380   *
381   * @throws  LDAPPersistException  If the object encoder does not support
382   *                                encoding values for the associated field
383   *                                type.
384   */
385  @NotNull()
386  AttributeTypeDefinition constructAttributeType()
387       throws LDAPPersistException
388  {
389    return constructAttributeType(DefaultOIDAllocator.getInstance());
390  }
391
392
393
394  /**
395   * Constructs a definition for an LDAP attribute type which may be added to
396   * the directory server schema to allow it to hold the value of the associated
397   * method.  Note that the object identifier used for the constructed attribute
398   * type definition is not required to be valid or unique.
399   *
400   * @param  a  The OID allocator to use to generate the object identifier.  It
401   *            must not be {@code null}.
402   *
403   * @return  The constructed attribute type definition.
404   *
405   * @throws  LDAPPersistException  If the object encoder does not support
406   *                                encoding values for the associated method
407   *                                type.
408   */
409  @NotNull()
410  AttributeTypeDefinition constructAttributeType(@NotNull final OIDAllocator a)
411       throws LDAPPersistException
412  {
413    return encoder.constructAttributeType(method, a);
414  }
415
416
417
418  /**
419   * Creates an attribute with the value returned by invoking the associated
420   * method on the provided object.
421   *
422   * @param  o  The object for which to invoke the associated method.
423   *
424   * @return  The attribute containing the encoded representation of the method
425   *          value, or {@code null} if the method returned {@code null}.
426   *
427   * @throws  LDAPPersistException  If a problem occurs while encoding the
428   *                                value of the associated field for the
429   *                                provided object.
430   */
431  @Nullable()
432  Attribute encode(@NotNull final Object o)
433            throws LDAPPersistException
434  {
435    try
436    {
437      final Object methodValue = method.invoke(o);
438      if (methodValue == null)
439      {
440        return null;
441      }
442
443      return encoder.encodeMethodValue(method, methodValue, attributeName);
444    }
445    catch (final Exception e)
446    {
447      Debug.debugException(e);
448      throw new LDAPPersistException(
449           ERR_GETTER_INFO_CANNOT_ENCODE.get(method.getName(),
450                containingClass.getName(), StaticUtils.getExceptionMessage(e)),
451           e);
452    }
453  }
454}