001 /*
002 * Copyright 2009-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2013 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.Field;
027 import java.lang.reflect.Modifier;
028 import java.util.List;
029
030 import com.unboundid.ldap.sdk.Attribute;
031 import com.unboundid.ldap.sdk.Entry;
032 import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
033 import com.unboundid.util.NotMutable;
034 import com.unboundid.util.ThreadSafety;
035 import com.unboundid.util.ThreadSafetyLevel;
036
037 import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
038 import static com.unboundid.util.Debug.*;
039 import static com.unboundid.util.StaticUtils.*;
040 import static com.unboundid.util.Validator.*;
041
042
043
044 /**
045 * This class provides a data structure that holds information about an
046 * annotated field.
047 */
048 @NotMutable()
049 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
050 public final class FieldInfo
051 implements Serializable
052 {
053 /**
054 * The serial version UID for this serializable class.
055 */
056 private static final long serialVersionUID = -5715642176677596417L;
057
058
059
060 // Indicates whether attempts to populate the associated field should fail if
061 // the LDAP attribute has a value that is not valid for the data type of the
062 // field.
063 private final boolean failOnInvalidValue;
064
065 // Indicates whether attempts to populate the associated field should fail if
066 // the LDAP attribute has multiple values but the field can only hold a single
067 // value.
068 private final boolean failOnTooManyValues;
069
070 // Indicates whether the associated field should be included in the entry
071 // created for an add operation.
072 private final boolean includeInAdd;
073
074 // Indicates whether the associated field should be considered for inclusion
075 // in the set of modifications used for modify operations.
076 private final boolean includeInModify;
077
078 // Indicates whether the associated field is part of the RDN.
079 private final boolean includeInRDN;
080
081 // Indicates whether the associated field is required when decoding.
082 private final boolean isRequiredForDecode;
083
084 // Indicates whether the associated field is required when encoding.
085 private final boolean isRequiredForEncode;
086
087 // Indicates whether the associated field should be lazily-loaded.
088 private final boolean lazilyLoad;
089
090 // Indicates whether the associated field supports multiple values.
091 private final boolean supportsMultipleValues;
092
093 // The class that contains the associated field.
094 private final Class<?> containingClass;
095
096 // The field with which this object is associated.
097 private final Field field;
098
099 // The filter usage for the associated field.
100 private final FilterUsage filterUsage;
101
102 // The encoder used for this field.
103 private final ObjectEncoder encoder;
104
105 // The name of the associated attribute type.
106 private final String attributeName;
107
108 // The default values for the field to use for object instantiation.
109 private final String[] defaultDecodeValues;
110
111 // The default values for the field to use for add operations.
112 private final String[] defaultEncodeValues;
113
114 // The names of the object classes for the associated attribute.
115 private final String[] objectClasses;
116
117
118
119 /**
120 * Creates a new field info object from the provided field.
121 *
122 * @param f The field to use to create this object. It must not be
123 * {@code null} and it must be marked with the {@code LDAPField}
124 * annotation.
125 * @param c The class which holds the field. It must not be {@code null}
126 * and it must be marked with the {@code LDAPObject} annotation.
127 *
128 * @throws LDAPPersistException If a problem occurs while processing the
129 * given field.
130 */
131 FieldInfo(final Field f, final Class<?> c)
132 throws LDAPPersistException
133 {
134 ensureNotNull(f, c);
135
136 field = f;
137 f.setAccessible(true);
138
139 final LDAPField a = f.getAnnotation(LDAPField.class);
140 if (a == null)
141 {
142 throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_NOT_ANNOTATED.get(
143 f.getName(), c.getName()));
144 }
145
146 final LDAPObject o = c.getAnnotation(LDAPObject.class);
147 if (o == null)
148 {
149 throw new LDAPPersistException(ERR_FIELD_INFO_CLASS_NOT_ANNOTATED.get(
150 c.getName()));
151 }
152
153 containingClass = c;
154 failOnInvalidValue = a.failOnInvalidValue();
155 includeInRDN = a.inRDN();
156 includeInAdd = (includeInRDN || a.inAdd());
157 includeInModify = ((! includeInRDN) && a.inModify());
158 filterUsage = a.filterUsage();
159 lazilyLoad = a.lazilyLoad();
160 isRequiredForDecode = (a.requiredForDecode() && (! lazilyLoad));
161 isRequiredForEncode = (includeInRDN || a.requiredForEncode());
162 defaultDecodeValues = a.defaultDecodeValue();
163 defaultEncodeValues = a.defaultEncodeValue();
164
165 if (lazilyLoad)
166 {
167 if (defaultDecodeValues.length > 0)
168 {
169 throw new LDAPPersistException(
170 ERR_FIELD_INFO_LAZY_WITH_DEFAULT_DECODE.get(f.getName(),
171 c.getName()));
172 }
173
174 if (defaultEncodeValues.length > 0)
175 {
176 throw new LDAPPersistException(
177 ERR_FIELD_INFO_LAZY_WITH_DEFAULT_ENCODE.get(f.getName(),
178 c.getName()));
179 }
180
181 if (includeInRDN)
182 {
183 throw new LDAPPersistException(ERR_FIELD_INFO_LAZY_IN_RDN.get(
184 f.getName(), c.getName()));
185 }
186 }
187
188 final int modifiers = f.getModifiers();
189 if (Modifier.isFinal(modifiers))
190 {
191 throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_FINAL.get(
192 f.getName(), c.getName()));
193 }
194
195 if (Modifier.isStatic(modifiers))
196 {
197 throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_STATIC.get(
198 f.getName(), c.getName()));
199 }
200
201 try
202 {
203 encoder = a.encoderClass().newInstance();
204 }
205 catch (Exception e)
206 {
207 debugException(e);
208 throw new LDAPPersistException(ERR_FIELD_INFO_CANNOT_GET_ENCODER.get(
209 a.encoderClass().getName(), f.getName(), c.getName(),
210 getExceptionMessage(e)), e);
211 }
212
213 if (! encoder.supportsType(f.getGenericType()))
214 {
215 throw new LDAPPersistException(
216 ERR_FIELD_INFO_ENCODER_UNSUPPORTED_TYPE.get(
217 encoder.getClass().getName(), f.getName(), c.getName(),
218 f.getGenericType()));
219 }
220
221 supportsMultipleValues = encoder.supportsMultipleValues(f);
222 if (supportsMultipleValues)
223 {
224 failOnTooManyValues = false;
225 }
226 else
227 {
228 failOnTooManyValues = a.failOnTooManyValues();
229 if (defaultDecodeValues.length > 1)
230 {
231 throw new LDAPPersistException(
232 ERR_FIELD_INFO_UNSUPPORTED_MULTIPLE_DEFAULT_DECODE_VALUES.get(
233 f.getName(), c.getName()));
234 }
235
236 if (defaultEncodeValues.length > 1)
237 {
238 throw new LDAPPersistException(
239 ERR_FIELD_INFO_UNSUPPORTED_MULTIPLE_DEFAULT_ENCODE_VALUES.get(
240 f.getName(), c.getName()));
241 }
242 }
243
244 final String attrName = a.attribute();
245 if ((attrName == null) || (attrName.length() == 0))
246 {
247 attributeName = f.getName();
248 }
249 else
250 {
251 attributeName = attrName;
252 }
253
254 final StringBuilder invalidReason = new StringBuilder();
255 if (! PersistUtils.isValidLDAPName(attributeName, true, invalidReason))
256 {
257 throw new LDAPPersistException(ERR_FIELD_INFO_INVALID_ATTR_NAME.get(
258 f.getName(), c.getName(), invalidReason.toString()));
259 }
260
261 final String structuralClass;
262 if (o.structuralClass().length() == 0)
263 {
264 structuralClass = getUnqualifiedClassName(c);
265 }
266 else
267 {
268 structuralClass = o.structuralClass();
269 }
270
271 final String[] ocs = a.objectClass();
272 if ((ocs == null) || (ocs.length == 0))
273 {
274 objectClasses = new String[] { structuralClass };
275 }
276 else
277 {
278 objectClasses = ocs;
279 }
280
281 for (final String s : objectClasses)
282 {
283 if (! s.equalsIgnoreCase(structuralClass))
284 {
285 boolean found = false;
286 for (final String oc : o.auxiliaryClass())
287 {
288 if (s.equalsIgnoreCase(oc))
289 {
290 found = true;
291 break;
292 }
293 }
294
295 if (! found)
296 {
297 throw new LDAPPersistException(ERR_FIELD_INFO_INVALID_OC.get(
298 f.getName(), c.getName(), s));
299 }
300 }
301 }
302 }
303
304
305
306 /**
307 * Retrieves the field with which this object is associated.
308 *
309 * @return The field with which this object is associated.
310 */
311 public Field getField()
312 {
313 return field;
314 }
315
316
317
318 /**
319 * Retrieves the class that is marked with the {@link LDAPObject} annotation
320 * and contains the associated field.
321 *
322 * @return The class that contains the associated field.
323 */
324 public Class<?> getContainingClass()
325 {
326 return containingClass;
327 }
328
329
330
331 /**
332 * Indicates whether attempts to initialize an object should fail if the LDAP
333 * attribute has a value that cannot be stored in the associated field.
334 *
335 * @return {@code true} if an exception should be thrown if an LDAP attribute
336 * has a value that cannot be assigned to the associated field, or
337 * {@code false} if the field should remain uninitialized.
338 */
339 public boolean failOnInvalidValue()
340 {
341 return failOnInvalidValue;
342 }
343
344
345
346 /**
347 * Indicates whether attempts to initialize an object should fail if the
348 * LDAP attribute has multiple values but the associated field can only hold a
349 * single value. Note that the value returned from this method may be
350 * {@code false} even when the annotation has a value of {@code true} if the
351 * associated field supports multiple values.
352 *
353 * @return {@code true} if an exception should be thrown if an attribute has
354 * too many values to hold in the associated field, or {@code false}
355 * if the first value returned should be assigned to the field.
356 */
357 public boolean failOnTooManyValues()
358 {
359 return failOnTooManyValues;
360 }
361
362
363
364 /**
365 * Indicates whether the associated field should be included in entries
366 * generated for add operations. Note that the value returned from this
367 * method may be {@code true} even when the annotation has a value of
368 * {@code false} if the associated field is to be included in entry RDNs.
369 *
370 * @return {@code true} if the associated field should be included in entries
371 * generated for add operations, or {@code false} if not.
372 */
373 public boolean includeInAdd()
374 {
375 return includeInAdd;
376 }
377
378
379
380 /**
381 * Indicates whether the associated field should be considered for inclusion
382 * in the set of modifications generated for modify operations. Note that the
383 * value returned from this method may be {@code false} even when the
384 * annotation has a value of {@code true} for the {@code inModify} element if
385 * the associated field is to be included in entry RDNs.
386 *
387 * @return {@code true} if the associated field should be considered for
388 * inclusion in the set of modifications generated for modify
389 * operations, or {@code false} if not.
390 */
391 public boolean includeInModify()
392 {
393 return includeInModify;
394 }
395
396
397
398 /**
399 * Indicates whether the associated field should be used to generate entry
400 * RDNs.
401 *
402 * @return {@code true} if the associated field should be used to generate
403 * entry RDNs, or {@code false} if not.
404 */
405 public boolean includeInRDN()
406 {
407 return includeInRDN;
408 }
409
410
411
412 /**
413 * Retrieves the filter usage for the associated field.
414 *
415 * @return The filter usage for the associated field.
416 */
417 public FilterUsage getFilterUsage()
418 {
419 return filterUsage;
420 }
421
422
423
424 /**
425 * Indicates whether the associated field should be considered required for
426 * decode operations.
427 *
428 * @return {@code true} if the associated field should be considered required
429 * for decode operations, or {@code false} if not.
430 */
431 public boolean isRequiredForDecode()
432 {
433 return isRequiredForDecode;
434 }
435
436
437
438 /**
439 * Indicates whether the associated field should be considered required for
440 * encode operations. Note that the value returned from this method may be
441 * {@code true} even when the annotation has a value of {@code true} for the
442 * {@code requiredForEncode} element if the associated field is to be included
443 * in entry RDNs.
444 *
445 * @return {@code true} if the associated field should be considered required
446 * for encode operations, or {@code false} if not.
447 */
448 public boolean isRequiredForEncode()
449 {
450 return isRequiredForEncode;
451 }
452
453
454
455 /**
456 * Indicates whether the associated field should be lazily-loaded.
457 *
458 * @return {@code true} if the associated field should be lazily-loaded, or
459 * {@code false} if not.
460 */
461 public boolean lazilyLoad()
462 {
463 return lazilyLoad;
464 }
465
466
467
468 /**
469 * Retrieves the encoder that should be used for the associated field.
470 *
471 * @return The encoder that should be used for the associated field.
472 */
473 public ObjectEncoder getEncoder()
474 {
475 return encoder;
476 }
477
478
479
480 /**
481 * Retrieves the name of the LDAP attribute used to hold values for the
482 * associated field.
483 *
484 * @return The name of the LDAP attribute used to hold values for the
485 * associated field.
486 */
487 public String getAttributeName()
488 {
489 return attributeName;
490 }
491
492
493
494 /**
495 * Retrieves the set of default values that should be assigned to the
496 * associated field if there are no values for the corresponding attribute in
497 * the LDAP entry.
498 *
499 * @return The set of default values for use when instantiating the object,
500 * or an empty array if no default values are defined.
501 */
502 public String[] getDefaultDecodeValues()
503 {
504 return defaultDecodeValues;
505 }
506
507
508
509 /**
510 * Retrieves the set of default values that should be used when creating an
511 * entry for an add operation if the associated field does not itself have any
512 * values.
513 *
514 * @return The set of default values for use in add operations, or an empty
515 * array if no default values are defined.
516 */
517 public String[] getDefaultEncodeValues()
518 {
519 return defaultEncodeValues;
520 }
521
522
523
524 /**
525 * Retrieves the names of the object classes containing the associated
526 * attribute.
527 *
528 * @return The names of the object classes containing the associated
529 * attribute.
530 */
531 public String[] getObjectClasses()
532 {
533 return objectClasses;
534 }
535
536
537
538 /**
539 * Indicates whether the associated field can hold multiple values.
540 *
541 * @return {@code true} if the associated field can hold multiple values, or
542 * {@code false} if not.
543 */
544 public boolean supportsMultipleValues()
545 {
546 return supportsMultipleValues;
547 }
548
549
550
551 /**
552 * Constructs a definition for an LDAP attribute type which may be added to
553 * the directory server schema to allow it to hold the value of the associated
554 * field. Note that the object identifier used for the constructed attribute
555 * type definition is not required to be valid or unique.
556 *
557 * @return The constructed attribute type definition.
558 *
559 * @throws LDAPPersistException If the object encoder does not support
560 * encoding values for the associated field
561 * type.
562 */
563 AttributeTypeDefinition constructAttributeType()
564 throws LDAPPersistException
565 {
566 return constructAttributeType(DefaultOIDAllocator.getInstance());
567 }
568
569
570
571 /**
572 * Constructs a definition for an LDAP attribute type which may be added to
573 * the directory server schema to allow it to hold the value of the associated
574 * field. Note that the object identifier used for the constructed attribute
575 * type definition is not required to be valid or unique.
576 *
577 * @param a The OID allocator to use to generate the object identifier. It
578 * must not be {@code null}.
579 *
580 * @return The constructed attribute type definition.
581 *
582 * @throws LDAPPersistException If the object encoder does not support
583 * encoding values for the associated field
584 * type.
585 */
586 AttributeTypeDefinition constructAttributeType(final OIDAllocator a)
587 throws LDAPPersistException
588 {
589 return encoder.constructAttributeType(field, a);
590 }
591
592
593
594 /**
595 * Encodes the value for the associated field from the provided object to an
596 * attribute.
597 *
598 * @param o The object containing the field to be encoded.
599 * @param ignoreRequiredFlag Indicates whether to ignore the value of the
600 * {@code requiredForEncode} setting. If this is
601 * {@code true}, then this method will always
602 * return {@code null} if the field does not have
603 * a value even if this field is marked as
604 * required for encode processing.
605 *
606 * @return The attribute containing the encoded representation of the field
607 * value if it is non-{@code null}, an encoded representation of the
608 * default add values if the associated field is {@code null} but
609 * default values are defined, or {@code null} if the associated
610 * field is {@code null} and there are no default values.
611 *
612 * @throws LDAPPersistException If a problem occurs while encoding the
613 * value of the associated field for the
614 * provided object, or if the field is marked
615 * as required but is {@code null} and does not
616 * have any default add values.
617 */
618 Attribute encode(final Object o, final boolean ignoreRequiredFlag)
619 throws LDAPPersistException
620 {
621 try
622 {
623 final Object fieldValue = field.get(o);
624 if (fieldValue == null)
625 {
626 if (defaultEncodeValues.length > 0)
627 {
628 return new Attribute(attributeName, defaultEncodeValues);
629 }
630
631 if (isRequiredForEncode && (! ignoreRequiredFlag))
632 {
633 throw new LDAPPersistException(
634 ERR_FIELD_INFO_MISSING_REQUIRED_VALUE.get(field.getName(),
635 containingClass.getName()));
636 }
637
638 return null;
639 }
640
641 return encoder.encodeFieldValue(field, fieldValue, attributeName);
642 }
643 catch (LDAPPersistException lpe)
644 {
645 debugException(lpe);
646 throw lpe;
647 }
648 catch (Exception e)
649 {
650 debugException(e);
651 throw new LDAPPersistException(ERR_FIELD_INFO_CANNOT_ENCODE.get(
652 field.getName(), containingClass.getName(), getExceptionMessage(e)),
653 e);
654 }
655 }
656
657
658
659 /**
660 * Sets the value of the associated field in the given object from the
661 * information contained in the provided attribute.
662 *
663 * @param o The object for which to update the associated
664 * field.
665 * @param e The entry being decoded.
666 * @param failureReasons A list to which information about any failures
667 * may be appended.
668 *
669 * @return {@code true} if the decode process was completely successful, or
670 * {@code false} if there were one or more failures.
671 */
672 boolean decode(final Object o, final Entry e,
673 final List<String> failureReasons)
674 {
675 boolean successful = true;
676
677 Attribute a = e.getAttribute(attributeName);
678 if ((a == null) || (! a.hasValue()))
679 {
680 if (defaultDecodeValues.length > 0)
681 {
682 a = new Attribute(attributeName, defaultDecodeValues);
683 }
684 else
685 {
686 if (isRequiredForDecode)
687 {
688 successful = false;
689 failureReasons.add(ERR_FIELD_INFO_MISSING_REQUIRED_ATTRIBUTE.get(
690 containingClass.getName(), e.getDN(), attributeName,
691 field.getName()));
692 }
693
694 try
695 {
696 encoder.setNull(field, o);
697 }
698 catch (final LDAPPersistException lpe)
699 {
700 debugException(lpe);
701 successful = false;
702 failureReasons.add(lpe.getMessage());
703 }
704
705 return successful;
706 }
707 }
708
709 if (failOnTooManyValues && (a.size() > 1))
710 {
711 successful = false;
712 failureReasons.add(ERR_FIELD_INFO_FIELD_NOT_MULTIVALUED.get(a.getName(),
713 field.getName(), containingClass.getName()));
714 }
715
716 try
717 {
718 encoder.decodeField(field, o, a);
719 }
720 catch (LDAPPersistException lpe)
721 {
722 debugException(lpe);
723 if (failOnInvalidValue)
724 {
725 successful = false;
726 failureReasons.add(lpe.getMessage());
727 }
728 }
729
730 return successful;
731 }
732 }