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