001 /*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2014 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 */
021 package com.unboundid.ldap.sdk.persist;
022
023
024
025 import java.io.Serializable;
026 import java.lang.reflect.Method;
027 import java.lang.reflect.Modifier;
028 import java.lang.reflect.Type;
029
030 import com.unboundid.ldap.sdk.Attribute;
031 import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
032 import com.unboundid.util.NotMutable;
033 import com.unboundid.util.ThreadSafety;
034 import com.unboundid.util.ThreadSafetyLevel;
035
036 import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
037 import static com.unboundid.util.Debug.*;
038 import static com.unboundid.util.StaticUtils.*;
039 import 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)
049 public 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 }