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