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