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