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}