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.Modifier; 043import java.lang.reflect.Type; 044 045import com.unboundid.ldap.sdk.Attribute; 046import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 047import com.unboundid.util.Debug; 048import com.unboundid.util.NotMutable; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.Nullable; 051import com.unboundid.util.StaticUtils; 052import com.unboundid.util.ThreadSafety; 053import com.unboundid.util.ThreadSafetyLevel; 054import com.unboundid.util.Validator; 055 056import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 057 058 059 060/** 061 * This class provides a data structure that holds information about an 062 * annotated getter method. 063 */ 064@NotMutable() 065@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 066public final class GetterInfo 067 implements Serializable 068{ 069 /** 070 * The serial version UID for this serializable class. 071 */ 072 private static final long serialVersionUID = 1578187843924054389L; 073 074 075 076 // Indicates whether the associated method value should be included in the 077 // entry created for an add operation. 078 private final boolean includeInAdd; 079 080 // Indicates whether the associated method value should be considered for 081 // inclusion in the set of modifications used for modify operations. 082 private final boolean includeInModify; 083 084 // Indicates whether the associated method value is part of the RDN. 085 private final boolean includeInRDN; 086 087 // The class that contains the associated method. 088 @NotNull private final Class<?> containingClass; 089 090 // The filter usage for the associated method. 091 @NotNull private final FilterUsage filterUsage; 092 093 // The method with which this object is associated. 094 @NotNull private final Method method; 095 096 // The encoder used for this method. 097 @NotNull private final ObjectEncoder encoder; 098 099 // The name of the associated attribute type. 100 @NotNull private final String attributeName; 101 102 // The names of the object classes for the associated attribute. 103 @NotNull private final String[] objectClasses; 104 105 106 107 /** 108 * Creates a new getter info object from the provided method. 109 * 110 * @param m The method to use to create this object. 111 * @param c The class which holds the method. 112 * 113 * @throws LDAPPersistException If a problem occurs while processing the 114 * given method. 115 */ 116 GetterInfo(@NotNull final Method m, @NotNull final Class<?> c) 117 throws LDAPPersistException 118 { 119 Validator.ensureNotNull(m, c); 120 121 method = m; 122 m.setAccessible(true); 123 124 final LDAPGetter a = m.getAnnotation(LDAPGetter.class); 125 if (a == null) 126 { 127 throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_NOT_ANNOTATED.get( 128 m.getName(), c.getName())); 129 } 130 131 final LDAPObject o = c.getAnnotation(LDAPObject.class); 132 if (o == null) 133 { 134 throw new LDAPPersistException(ERR_GETTER_INFO_CLASS_NOT_ANNOTATED.get( 135 c.getName())); 136 } 137 138 containingClass = c; 139 includeInRDN = a.inRDN(); 140 includeInAdd = (includeInRDN || a.inAdd()); 141 includeInModify = ((! includeInRDN) && a.inModify()); 142 filterUsage = a.filterUsage(); 143 144 final int modifiers = m.getModifiers(); 145 if (Modifier.isStatic(modifiers)) 146 { 147 throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_STATIC.get( 148 m.getName(), c.getName())); 149 } 150 151 final Type[] params = m.getGenericParameterTypes(); 152 if (params.length > 0) 153 { 154 throw new LDAPPersistException(ERR_GETTER_INFO_METHOD_TAKES_ARGUMENTS.get( 155 m.getName(), c.getName())); 156 } 157 158 try 159 { 160 encoder = a.encoderClass().newInstance(); 161 } 162 catch (final Exception e) 163 { 164 Debug.debugException(e); 165 throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_GET_ENCODER.get( 166 a.encoderClass().getName(), m.getName(), c.getName(), 167 StaticUtils.getExceptionMessage(e)), e); 168 } 169 170 if (! encoder.supportsType(m.getGenericReturnType())) 171 { 172 throw new LDAPPersistException( 173 ERR_GETTER_INFO_ENCODER_UNSUPPORTED_TYPE.get( 174 encoder.getClass().getName(), m.getName(), c.getName(), 175 String.valueOf(m.getGenericReturnType()))); 176 } 177 178 final String structuralClass; 179 if (o.structuralClass().isEmpty()) 180 { 181 structuralClass = StaticUtils.getUnqualifiedClassName(c); 182 } 183 else 184 { 185 structuralClass = o.structuralClass(); 186 } 187 188 final String[] ocs = a.objectClass(); 189 if ((ocs == null) || (ocs.length == 0)) 190 { 191 objectClasses = new String[] { structuralClass }; 192 } 193 else 194 { 195 objectClasses = ocs; 196 } 197 198 for (final String s : objectClasses) 199 { 200 if (! s.equalsIgnoreCase(structuralClass)) 201 { 202 boolean found = false; 203 for (final String oc : o.auxiliaryClass()) 204 { 205 if (s.equalsIgnoreCase(oc)) 206 { 207 found = true; 208 break; 209 } 210 } 211 212 if (! found) 213 { 214 throw new LDAPPersistException(ERR_GETTER_INFO_INVALID_OC.get( 215 m.getName(), c.getName(), s)); 216 } 217 } 218 } 219 220 final String attrName = a.attribute(); 221 if ((attrName == null) || attrName.isEmpty()) 222 { 223 final String methodName = m.getName(); 224 if (methodName.startsWith("get") && (methodName.length() >= 4)) 225 { 226 attributeName = StaticUtils.toInitialLowerCase(methodName.substring(3)); 227 } 228 else 229 { 230 throw new LDAPPersistException(ERR_GETTER_INFO_CANNOT_INFER_ATTR.get( 231 methodName, c.getName())); 232 } 233 } 234 else 235 { 236 attributeName = attrName; 237 } 238 } 239 240 241 242 /** 243 * Retrieves the method with which this object is associated. 244 * 245 * @return The method with which this object is associated. 246 */ 247 @NotNull() 248 public Method getMethod() 249 { 250 return method; 251 } 252 253 254 255 /** 256 * Retrieves the class that is marked with the {@link LDAPObject} annotation 257 * and contains the associated field. 258 * 259 * @return The class that contains the associated field. 260 */ 261 @NotNull() 262 public Class<?> getContainingClass() 263 { 264 return containingClass; 265 } 266 267 268 269 /** 270 * Indicates whether the associated method value should be included in entries 271 * generated for add operations. Note that the value returned from this 272 * method may be {@code true} even when the annotation has a value of 273 * {@code false} if the associated field is to be included in entry RDNs. 274 * 275 * @return {@code true} if the associated method value should be included in 276 * entries generated for add operations, or {@code false} if not. 277 */ 278 public boolean includeInAdd() 279 { 280 return includeInAdd; 281 } 282 283 284 285 /** 286 * Indicates whether the associated method value should be considered for 287 * inclusion in the set of modifications generated for modify operations. 288 * Note that the value returned from this method may be {@code false} even 289 * when the annotation have a value of {@code true} if the associated field is 290 * to be included in entry RDNs. 291 * 292 * @return {@code true} if the associated method value should be considered 293 * for inclusion in the set of modifications generated for modify 294 * operations, or {@code false} if not. 295 */ 296 public boolean includeInModify() 297 { 298 return includeInModify; 299 } 300 301 302 303 /** 304 * Indicates whether the associated method value should be used to generate 305 * entry RDNs. 306 * 307 * @return {@code true} if the associated method value should be used to 308 * generate entry RDNs, or {@code false} if not. 309 */ 310 public boolean includeInRDN() 311 { 312 return includeInRDN; 313 } 314 315 316 317 /** 318 * Retrieves the filter usage for the associated method. 319 * 320 * @return The filter usage for the associated method. 321 */ 322 @NotNull() 323 public FilterUsage getFilterUsage() 324 { 325 return filterUsage; 326 } 327 328 329 330 /** 331 * Retrieves the encoder that should be used for the associated method. 332 * 333 * @return The encoder that should be used for the associated method. 334 */ 335 @NotNull() 336 public ObjectEncoder getEncoder() 337 { 338 return encoder; 339 } 340 341 342 343 /** 344 * Retrieves the name of the LDAP attribute used to hold values for the 345 * associated method. 346 * 347 * @return The name of the LDAP attribute used to hold values for the 348 * associated method. 349 */ 350 @NotNull() 351 public String getAttributeName() 352 { 353 return attributeName; 354 } 355 356 357 358 /** 359 * Retrieves the names of the object classes containing the associated 360 * attribute. 361 * 362 * @return The names of the object classes containing the associated 363 * attribute. 364 */ 365 @NotNull() 366 public String[] getObjectClasses() 367 { 368 return objectClasses; 369 } 370 371 372 373 /** 374 * Constructs a definition for an LDAP attribute type which may be added to 375 * the directory server schema to allow it to hold the value of the associated 376 * method. Note that the object identifier used for the constructed attribute 377 * type definition is not required to be valid or unique. 378 * 379 * @return The constructed attribute type definition. 380 * 381 * @throws LDAPPersistException If the object encoder does not support 382 * encoding values for the associated field 383 * type. 384 */ 385 @NotNull() 386 AttributeTypeDefinition constructAttributeType() 387 throws LDAPPersistException 388 { 389 return constructAttributeType(DefaultOIDAllocator.getInstance()); 390 } 391 392 393 394 /** 395 * Constructs a definition for an LDAP attribute type which may be added to 396 * the directory server schema to allow it to hold the value of the associated 397 * method. Note that the object identifier used for the constructed attribute 398 * type definition is not required to be valid or unique. 399 * 400 * @param a The OID allocator to use to generate the object identifier. It 401 * must not be {@code null}. 402 * 403 * @return The constructed attribute type definition. 404 * 405 * @throws LDAPPersistException If the object encoder does not support 406 * encoding values for the associated method 407 * type. 408 */ 409 @NotNull() 410 AttributeTypeDefinition constructAttributeType(@NotNull final OIDAllocator a) 411 throws LDAPPersistException 412 { 413 return encoder.constructAttributeType(method, a); 414 } 415 416 417 418 /** 419 * Creates an attribute with the value returned by invoking the associated 420 * method on the provided object. 421 * 422 * @param o The object for which to invoke the associated method. 423 * 424 * @return The attribute containing the encoded representation of the method 425 * value, or {@code null} if the method returned {@code null}. 426 * 427 * @throws LDAPPersistException If a problem occurs while encoding the 428 * value of the associated field for the 429 * provided object. 430 */ 431 @Nullable() 432 Attribute encode(@NotNull final Object o) 433 throws LDAPPersistException 434 { 435 try 436 { 437 final Object methodValue = method.invoke(o); 438 if (methodValue == null) 439 { 440 return null; 441 } 442 443 return encoder.encodeMethodValue(method, methodValue, attributeName); 444 } 445 catch (final Exception e) 446 { 447 Debug.debugException(e); 448 throw new LDAPPersistException( 449 ERR_GETTER_INFO_CANNOT_ENCODE.get(method.getName(), 450 containingClass.getName(), StaticUtils.getExceptionMessage(e)), 451 e); 452 } 453 } 454}