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