001/* 002 * Copyright 2009-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-2020 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-2020 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.StaticUtils; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.Validator; 053 054import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 055 056 057 058/** 059 * This class provides a data structure that holds information about an 060 * annotated setter method. 061 */ 062@NotMutable() 063@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 064public final class SetterInfo 065 implements Serializable 066{ 067 /** 068 * The serial version UID for this serializable class. 069 */ 070 private static final long serialVersionUID = -1743750276508505946L; 071 072 073 074 // Indicates whether attempts to invoke the associated method should fail if 075 // the LDAP attribute has a value that is not valid for the data type of the 076 // method argument. 077 private final boolean failOnInvalidValue; 078 079 // Indicates whether attempts to invoke the associated method should fail if 080 // the LDAP attribute has multiple values but the method argument can only 081 // hold a single value. 082 private final boolean failOnTooManyValues; 083 084 // Indicates whether the associated method takes an argument that supports 085 // multiple values. 086 private final boolean supportsMultipleValues; 087 088 // The class that contains the associated method. 089 private final Class<?> containingClass; 090 091 // The method with which this object is associated. 092 private final Method method; 093 094 // The encoder used for this method. 095 private final ObjectEncoder encoder; 096 097 // The name of the associated attribute type. 098 private final String attributeName; 099 100 101 102 /** 103 * Creates a new setter info object from the provided method. 104 * 105 * @param m The method to use to create this object. 106 * @param c The class which holds the method. 107 * 108 * @throws LDAPPersistException If a problem occurs while processing the 109 * given method. 110 */ 111 SetterInfo(final Method m, final Class<?> c) 112 throws LDAPPersistException 113 { 114 Validator.ensureNotNull(m, c); 115 116 method = m; 117 m.setAccessible(true); 118 119 final LDAPSetter a = m.getAnnotation(LDAPSetter.class); 120 if (a == null) 121 { 122 throw new LDAPPersistException(ERR_SETTER_INFO_METHOD_NOT_ANNOTATED.get( 123 m.getName(), c.getName())); 124 } 125 126 final LDAPObject o = c.getAnnotation(LDAPObject.class); 127 if (o == null) 128 { 129 throw new LDAPPersistException(ERR_SETTER_INFO_CLASS_NOT_ANNOTATED.get( 130 c.getName())); 131 } 132 133 containingClass = c; 134 failOnInvalidValue = a.failOnInvalidValue(); 135 136 final Type[] params = m.getGenericParameterTypes(); 137 if (params.length != 1) 138 { 139 throw new LDAPPersistException( 140 ERR_SETTER_INFO_METHOD_DOES_NOT_TAKE_ONE_ARGUMENT.get(m.getName(), 141 c.getName())); 142 } 143 144 try 145 { 146 encoder = a.encoderClass().newInstance(); 147 } 148 catch (final Exception e) 149 { 150 Debug.debugException(e); 151 throw new LDAPPersistException( 152 ERR_SETTER_INFO_CANNOT_GET_ENCODER.get(a.encoderClass().getName(), 153 m.getName(), c.getName(), StaticUtils.getExceptionMessage(e)), 154 e); 155 } 156 157 if (! encoder.supportsType(params[0])) 158 { 159 throw new LDAPPersistException( 160 ERR_SETTER_INFO_ENCODER_UNSUPPORTED_TYPE.get( 161 encoder.getClass().getName(), m.getName(), c.getName(), 162 String.valueOf(params[0]))); 163 } 164 165 supportsMultipleValues = encoder.supportsMultipleValues(m); 166 if (supportsMultipleValues) 167 { 168 failOnTooManyValues = false; 169 } 170 else 171 { 172 failOnTooManyValues = a.failOnTooManyValues(); 173 } 174 175 final String attrName = a.attribute(); 176 if ((attrName == null) || attrName.isEmpty()) 177 { 178 final String methodName = m.getName(); 179 if (methodName.startsWith("set") && (methodName.length() >= 4)) 180 { 181 attributeName = StaticUtils.toInitialLowerCase(methodName.substring(3)); 182 } 183 else 184 { 185 throw new LDAPPersistException(ERR_SETTER_INFO_CANNOT_INFER_ATTR.get( 186 methodName, c.getName())); 187 } 188 } 189 else 190 { 191 attributeName = attrName; 192 } 193 } 194 195 196 197 /** 198 * Retrieves the method with which this object is associated. 199 * 200 * @return The method with which this object is associated. 201 */ 202 public Method getMethod() 203 { 204 return method; 205 } 206 207 208 209 /** 210 * Retrieves the class that is marked with the {@link LDAPObject} annotation 211 * and contains the associated field. 212 * 213 * @return The class that contains the associated field. 214 */ 215 public Class<?> getContainingClass() 216 { 217 return containingClass; 218 } 219 220 221 222 /** 223 * Indicates whether attempts to initialize an object should fail if the LDAP 224 * attribute has a value that cannot be represented in the argument type for 225 * the associated method. 226 * 227 * @return {@code true} if an exception should be thrown if an LDAP attribute 228 * has a value that cannot be provided as an argument to the 229 * associated method, or {@code false} if the method should not be 230 * invoked. 231 */ 232 public boolean failOnInvalidValue() 233 { 234 return failOnInvalidValue; 235 } 236 237 238 239 /** 240 * Indicates whether attempts to initialize an object should fail if the 241 * LDAP attribute has multiple values but the associated method argument can 242 * only hold a single value. Note that the value returned from this method 243 * may be {@code false} even when the annotation has a value of {@code true} 244 * if the associated method takes an argument that supports multiple values. 245 * 246 * @return {@code true} if an exception should be thrown if an attribute has 247 * too many values to provide to the associated method, or 248 * {@code false} if the first value returned should be provided as an 249 * argument to the associated method. 250 */ 251 public boolean failOnTooManyValues() 252 { 253 return failOnTooManyValues; 254 } 255 256 257 258 /** 259 * Retrieves the encoder that should be used for the associated method. 260 * 261 * @return The encoder that should be used for the associated method. 262 */ 263 public ObjectEncoder getEncoder() 264 { 265 return encoder; 266 } 267 268 269 270 /** 271 * Retrieves the name of the LDAP attribute used to hold values for the 272 * associated method. 273 * 274 * @return The name of the LDAP attribute used to hold values for the 275 * associated method. 276 */ 277 public String getAttributeName() 278 { 279 return attributeName; 280 } 281 282 283 284 /** 285 * Indicates whether the associated method takes an argument that can hold 286 * multiple values. 287 * 288 * @return {@code true} if the associated method takes an argument that can 289 * hold multiple values, or {@code false} if not. 290 */ 291 public boolean supportsMultipleValues() 292 { 293 return supportsMultipleValues; 294 } 295 296 297 298 /** 299 * Invokes the setter method on the provided object with the value from the 300 * given attribute. 301 * 302 * @param o The object for which to invoke the setter method. 303 * @param e The entry being decoded. 304 * @param failureReasons A list to which information about any failures 305 * may be appended. 306 * 307 * @return {@code true} if the decode process was completely successful, or 308 * {@code false} if there were one or more failures. 309 */ 310 boolean invokeSetter(final Object o, final Entry e, 311 final List<String> failureReasons) 312 { 313 boolean successful = true; 314 315 final Attribute a = e.getAttribute(attributeName); 316 if ((a == null) || (! a.hasValue())) 317 { 318 try 319 { 320 encoder.setNull(method, o); 321 } 322 catch (final LDAPPersistException lpe) 323 { 324 Debug.debugException(lpe); 325 successful = false; 326 failureReasons.add(lpe.getMessage()); 327 } 328 329 return successful; 330 } 331 332 if (failOnTooManyValues && (a.size() > 1)) 333 { 334 successful = false; 335 failureReasons.add(ERR_SETTER_INFO_METHOD_NOT_MULTIVALUED.get( 336 method.getName(), a.getName(), containingClass.getName())); 337 } 338 339 try 340 { 341 encoder.invokeSetter(method, o, a); 342 } 343 catch (final LDAPPersistException lpe) 344 { 345 Debug.debugException(lpe); 346 if (failOnInvalidValue) 347 { 348 successful = false; 349 failureReasons.add(lpe.getMessage()); 350 } 351 } 352 353 return successful; 354 } 355}