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