001/*
002 * Copyright 2009-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2018 Ping Identity Corporation
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.Type;
028import java.util.List;
029
030import com.unboundid.ldap.sdk.Attribute;
031import com.unboundid.ldap.sdk.Entry;
032import com.unboundid.util.Debug;
033import com.unboundid.util.NotMutable;
034import com.unboundid.util.StaticUtils;
035import com.unboundid.util.ThreadSafety;
036import com.unboundid.util.ThreadSafetyLevel;
037import com.unboundid.util.Validator;
038
039import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
040
041
042
043/**
044 * This class provides a data structure that holds information about an
045 * annotated setter method.
046 */
047@NotMutable()
048@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
049public final class SetterInfo
050       implements Serializable
051{
052  /**
053   * The serial version UID for this serializable class.
054   */
055  private static final long serialVersionUID = -1743750276508505946L;
056
057
058
059  // Indicates whether attempts to invoke the associated method should fail if
060  // the LDAP attribute has a value that is not valid for the data type of the
061  // method argument.
062  private final boolean failOnInvalidValue;
063
064  // Indicates whether attempts to invoke the associated method should fail if
065  // the LDAP attribute has multiple values but the method argument can only
066  // hold a single value.
067  private final boolean failOnTooManyValues;
068
069  // Indicates whether the associated method takes an argument that supports
070  // multiple values.
071  private final boolean supportsMultipleValues;
072
073  // The class that contains the associated method.
074  private final Class<?> containingClass;
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
086
087  /**
088   * Creates a new setter info object from the provided method.
089   *
090   * @param  m  The method to use to create this object.
091   * @param  c  The class which holds the method.
092   *
093   * @throws  LDAPPersistException  If a problem occurs while processing the
094   *                                given method.
095   */
096  SetterInfo(final Method m, final Class<?> c)
097       throws LDAPPersistException
098  {
099    Validator.ensureNotNull(m, c);
100
101    method = m;
102    m.setAccessible(true);
103
104    final LDAPSetter  a = m.getAnnotation(LDAPSetter.class);
105    if (a == null)
106    {
107      throw new LDAPPersistException(ERR_SETTER_INFO_METHOD_NOT_ANNOTATED.get(
108           m.getName(), c.getName()));
109    }
110
111    final LDAPObject o = c.getAnnotation(LDAPObject.class);
112    if (o == null)
113    {
114      throw new LDAPPersistException(ERR_SETTER_INFO_CLASS_NOT_ANNOTATED.get(
115           c.getName()));
116    }
117
118    containingClass    = c;
119    failOnInvalidValue = a.failOnInvalidValue();
120
121    final Type[] params = m.getGenericParameterTypes();
122    if (params.length != 1)
123    {
124      throw new LDAPPersistException(
125           ERR_SETTER_INFO_METHOD_DOES_NOT_TAKE_ONE_ARGUMENT.get(m.getName(),
126                c.getName()));
127    }
128
129    try
130    {
131      encoder = a.encoderClass().newInstance();
132    }
133    catch (final Exception e)
134    {
135      Debug.debugException(e);
136      throw new LDAPPersistException(
137           ERR_SETTER_INFO_CANNOT_GET_ENCODER.get(a.encoderClass().getName(),
138                m.getName(), c.getName(), StaticUtils.getExceptionMessage(e)),
139           e);
140    }
141
142    if (! encoder.supportsType(params[0]))
143    {
144      throw new LDAPPersistException(
145           ERR_SETTER_INFO_ENCODER_UNSUPPORTED_TYPE.get(
146                encoder.getClass().getName(), m.getName(), c.getName(),
147                String.valueOf(params[0])));
148    }
149
150    supportsMultipleValues = encoder.supportsMultipleValues(m);
151    if (supportsMultipleValues)
152    {
153      failOnTooManyValues = false;
154    }
155    else
156    {
157      failOnTooManyValues = a.failOnTooManyValues();
158    }
159
160    final String attrName = a.attribute();
161    if ((attrName == null) || attrName.isEmpty())
162    {
163      final String methodName = m.getName();
164      if (methodName.startsWith("set") && (methodName.length() >= 4))
165      {
166        attributeName = StaticUtils.toInitialLowerCase(methodName.substring(3));
167      }
168      else
169      {
170        throw new LDAPPersistException(ERR_SETTER_INFO_CANNOT_INFER_ATTR.get(
171             methodName, c.getName()));
172      }
173    }
174    else
175    {
176      attributeName = attrName;
177    }
178  }
179
180
181
182  /**
183   * Retrieves the method with which this object is associated.
184   *
185   * @return  The method with which this object is associated.
186   */
187  public Method getMethod()
188  {
189    return method;
190  }
191
192
193
194  /**
195   * Retrieves the class that is marked with the {@link LDAPObject} annotation
196   * and contains the associated field.
197   *
198   * @return  The class that contains the associated field.
199   */
200  public Class<?> getContainingClass()
201  {
202    return containingClass;
203  }
204
205
206
207  /**
208   * Indicates whether attempts to initialize an object should fail if the LDAP
209   * attribute has a value that cannot be represented in the argument type for
210   * the associated method.
211   *
212   * @return  {@code true} if an exception should be thrown if an LDAP attribute
213   *          has a value that cannot be provided as an argument to the
214   *          associated method, or {@code false} if the method should not be
215   *          invoked.
216   */
217  public boolean failOnInvalidValue()
218  {
219    return failOnInvalidValue;
220  }
221
222
223
224  /**
225   * Indicates whether attempts to initialize an object should fail if the
226   * LDAP attribute has multiple values but the associated method argument can
227   * only hold a single value.  Note that the value returned from this method
228   * may be {@code false} even when the annotation has a value of {@code true}
229   * if the associated method takes an argument that supports multiple values.
230   *
231   * @return  {@code true} if an exception should be thrown if an attribute has
232   *          too many values to provide to the associated method, or
233   *          {@code false} if the first value returned should be provided as an
234   *          argument to the associated method.
235   */
236  public boolean failOnTooManyValues()
237  {
238    return failOnTooManyValues;
239  }
240
241
242
243  /**
244   * Retrieves the encoder that should be used for the associated method.
245   *
246   * @return  The encoder that should be used for the associated method.
247   */
248  public ObjectEncoder getEncoder()
249  {
250    return encoder;
251  }
252
253
254
255  /**
256   * Retrieves the name of the LDAP attribute used to hold values for the
257   * associated method.
258   *
259   * @return  The name of the LDAP attribute used to hold values for the
260   *          associated method.
261   */
262  public String getAttributeName()
263  {
264    return attributeName;
265  }
266
267
268
269  /**
270   * Indicates whether the associated method takes an argument that can hold
271   * multiple values.
272   *
273   * @return  {@code true} if the associated method takes an argument that can
274   *          hold multiple values, or {@code false} if not.
275   */
276  public boolean supportsMultipleValues()
277  {
278    return supportsMultipleValues;
279  }
280
281
282
283  /**
284   * Invokes the setter method on the provided object with the value from the
285   * given attribute.
286   *
287   * @param  o               The object for which to invoke the setter method.
288   * @param  e               The entry being decoded.
289   * @param  failureReasons  A list to which information about any failures
290   *                         may be appended.
291   *
292   * @return  {@code true} if the decode process was completely successful, or
293   *          {@code false} if there were one or more failures.
294   */
295  boolean invokeSetter(final Object o, final Entry e,
296                       final List<String> failureReasons)
297  {
298    boolean successful = true;
299
300    final Attribute a = e.getAttribute(attributeName);
301    if ((a == null) || (! a.hasValue()))
302    {
303      try
304      {
305        encoder.setNull(method, o);
306      }
307      catch (final LDAPPersistException lpe)
308      {
309        Debug.debugException(lpe);
310        successful = false;
311        failureReasons.add(lpe.getMessage());
312      }
313
314      return successful;
315    }
316
317    if (failOnTooManyValues && (a.size() > 1))
318    {
319      successful = false;
320      failureReasons.add(ERR_SETTER_INFO_METHOD_NOT_MULTIVALUED.get(
321           method.getName(), a.getName(), containingClass.getName()));
322    }
323
324    try
325    {
326      encoder.invokeSetter(method, o, a);
327    }
328    catch (final LDAPPersistException lpe)
329    {
330      Debug.debugException(lpe);
331      if (failOnInvalidValue)
332      {
333        successful = false;
334        failureReasons.add(lpe.getMessage());
335      }
336    }
337
338    return successful;
339  }
340}