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 */
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 }