001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 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     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.io.Serializable;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Type;
028    import java.util.List;
029    
030    import com.unboundid.ldap.sdk.Attribute;
031    import com.unboundid.ldap.sdk.Entry;
032    import com.unboundid.util.NotMutable;
033    import com.unboundid.util.ThreadSafety;
034    import com.unboundid.util.ThreadSafetyLevel;
035    
036    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
037    import static com.unboundid.util.Debug.*;
038    import static com.unboundid.util.StaticUtils.*;
039    import static com.unboundid.util.Validator.*;
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)
049    public 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        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 (Exception e)
134        {
135          debugException(e);
136          throw new LDAPPersistException(ERR_SETTER_INFO_CANNOT_GET_ENCODER.get(
137               a.encoderClass().getName(), m.getName(), c.getName(),
138               getExceptionMessage(e)), e);
139        }
140    
141        if (! encoder.supportsType(params[0]))
142        {
143          throw new LDAPPersistException(
144               ERR_SETTER_INFO_ENCODER_UNSUPPORTED_TYPE.get(
145                    encoder.getClass().getName(), m.getName(), c.getName(),
146                    String.valueOf(params[0])));
147        }
148    
149        supportsMultipleValues = encoder.supportsMultipleValues(m);
150        if (supportsMultipleValues)
151        {
152          failOnTooManyValues = false;
153        }
154        else
155        {
156          failOnTooManyValues = a.failOnTooManyValues();
157        }
158    
159        final String attrName = a.attribute();
160        if ((attrName == null) || (attrName.length() == 0))
161        {
162          final String methodName = m.getName();
163          if (methodName.startsWith("set") && (methodName.length() >= 4))
164          {
165            attributeName = toInitialLowerCase(methodName.substring(3));
166          }
167          else
168          {
169            throw new LDAPPersistException(ERR_SETTER_INFO_CANNOT_INFER_ATTR.get(
170                 methodName, c.getName()));
171          }
172        }
173        else
174        {
175          attributeName = attrName;
176        }
177      }
178    
179    
180    
181      /**
182       * Retrieves the method with which this object is associated.
183       *
184       * @return  The method with which this object is associated.
185       */
186      public Method getMethod()
187      {
188        return method;
189      }
190    
191    
192    
193      /**
194       * Retrieves the class that is marked with the {@link LDAPObject} annotation
195       * and contains the associated field.
196       *
197       * @return  The class that contains the associated field.
198       */
199      public Class<?> getContainingClass()
200      {
201        return containingClass;
202      }
203    
204    
205    
206      /**
207       * Indicates whether attempts to initialize an object should fail if the LDAP
208       * attribute has a value that cannot be represented in the argument type for
209       * the associated method.
210       *
211       * @return  {@code true} if an exception should be thrown if an LDAP attribute
212       *          has a value that cannot be provided as an argument to the
213       *          associated method, or {@code false} if the method should not be
214       *          invoked.
215       */
216      public boolean failOnInvalidValue()
217      {
218        return failOnInvalidValue;
219      }
220    
221    
222    
223      /**
224       * Indicates whether attempts to initialize an object should fail if the
225       * LDAP attribute has multiple values but the associated method argument can
226       * only hold a single value.  Note that the value returned from this method
227       * may be {@code false} even when the annotation has a value of {@code true}
228       * if the associated method takes an argument that supports multiple values.
229       *
230       * @return  {@code true} if an exception should be thrown if an attribute has
231       *          too many values to provide to the associated method, or
232       *          {@code false} if the first value returned should be provided as an
233       *          argument to the associated method.
234       */
235      public boolean failOnTooManyValues()
236      {
237        return failOnTooManyValues;
238      }
239    
240    
241    
242      /**
243       * Retrieves the encoder that should be used for the associated method.
244       *
245       * @return  The encoder that should be used for the associated method.
246       */
247      public ObjectEncoder getEncoder()
248      {
249        return encoder;
250      }
251    
252    
253    
254      /**
255       * Retrieves the name of the LDAP attribute used to hold values for the
256       * associated method.
257       *
258       * @return  The name of the LDAP attribute used to hold values for the
259       *          associated method.
260       */
261      public String getAttributeName()
262      {
263        return attributeName;
264      }
265    
266    
267    
268      /**
269       * Indicates whether the associated method takes an argument that can hold
270       * multiple values.
271       *
272       * @return  {@code true} if the associated method takes an argument that can
273       *          hold multiple values, or {@code false} if not.
274       */
275      public boolean supportsMultipleValues()
276      {
277        return supportsMultipleValues;
278      }
279    
280    
281    
282      /**
283       * Invokes the setter method on the provided object with the value from the
284       * given attribute.
285       *
286       * @param  o               The object for which to invoke the setter method.
287       * @param  e               The entry being decoded.
288       * @param  failureReasons  A list to which information about any failures
289       *                         may be appended.
290       *
291       * @return  {@code true} if the decode process was completely successful, or
292       *          {@code false} if there were one or more failures.
293       */
294      boolean invokeSetter(final Object o, final Entry e,
295                           final List<String> failureReasons)
296      {
297        boolean successful = true;
298    
299        final Attribute a = e.getAttribute(attributeName);
300        if ((a == null) || (! a.hasValue()))
301        {
302          try
303          {
304            encoder.setNull(method, o);
305          }
306          catch (final LDAPPersistException lpe)
307          {
308            debugException(lpe);
309            successful = false;
310            failureReasons.add(lpe.getMessage());
311          }
312    
313          return successful;
314        }
315    
316        if (failOnTooManyValues && (a.size() > 1))
317        {
318          successful = false;
319          failureReasons.add(ERR_SETTER_INFO_METHOD_NOT_MULTIVALUED.get(
320               method.getName(), a.getName(), containingClass.getName()));
321        }
322    
323        try
324        {
325          encoder.invokeSetter(method, o, a);
326        }
327        catch (LDAPPersistException lpe)
328        {
329          debugException(lpe);
330          if (failOnInvalidValue)
331          {
332            successful = false;
333            failureReasons.add(lpe.getMessage());
334          }
335        }
336    
337        return successful;
338      }
339    }