001/* 002 * Copyright 2009-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2017 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 */ 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.NotMutable; 033import com.unboundid.util.ThreadSafety; 034import com.unboundid.util.ThreadSafetyLevel; 035 036import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 037import static com.unboundid.util.Debug.*; 038import static com.unboundid.util.StaticUtils.*; 039import 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) 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 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}