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