Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2009-2021 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2009-2021 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2009-2021 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk.persist;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.RDN;
import com.unboundid.ldap.sdk.ReadOnlyEntry;
import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
import com.unboundid.ldap.sdk.schema.ObjectClassType;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
/**
* This class provides a mechanism for validating, encoding, and decoding
* objects marked with the {@link LDAPObject} annotation type.
*
* @param The type of object handled by this class.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class LDAPObjectHandler
implements Serializable
{
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = -1480360011153517161L;
// The object class attribute to include in entries that are created.
@NotNull private final Attribute objectClassAttribute;
// The type of object handled by this class.
@NotNull private final Class type;
// The constructor to use to create a new instance of the class.
@NotNull private final Constructor constructor;
// The default parent DN for entries created from objects of the associated
// type.
@NotNull private final DN defaultParentDN;
// The field that will be used to hold the DN of the entry.
@Nullable private final Field dnField;
// The field that will be used to hold the entry contents.
@Nullable private final Field entryField;
// The LDAPObject annotation for the associated object.
@NotNull private final LDAPObject ldapObject;
// The LDAP object handler for the superclass, if applicable.
@Nullable private final LDAPObjectHandler super T> superclassHandler;
// The list of fields for with a filter usage of ALWAYS_ALLOWED.
@NotNull private final List alwaysAllowedFilterFields;
// The list of fields for with a filter usage of CONDITIONALLY_ALLOWED.
@NotNull private final List conditionallyAllowedFilterFields;
// The list of fields for with a filter usage of REQUIRED.
@NotNull private final List requiredFilterFields;
// The list of fields for this class that should be used to construct the RDN.
@NotNull private final List rdnFields;
// The list of getter methods for with a filter usage of ALWAYS_ALLOWED.
@NotNull private final List alwaysAllowedFilterGetters;
// The list of getter methods for with a filter usage of
// CONDITIONALLY_ALLOWED.
@NotNull private final List conditionallyAllowedFilterGetters;
// The list of getter methods for with a filter usage of REQUIRED.
@NotNull private final List requiredFilterGetters;
// The list of getters for this class that should be used to construct the
// RDN.
@NotNull private final List rdnGetters;
// The map of attribute names to their corresponding fields.
@NotNull private final Map fieldMap;
// The map of attribute names to their corresponding getter methods.
@NotNull private final Map getterMap;
// The map of attribute names to their corresponding setter methods.
@NotNull private final Map setterMap;
// The method that should be invoked on an object after all other decode
// processing has been performed.
@Nullable private final Method postDecodeMethod;
// The method that should be invoked on an object after all other encode
// processing has been performed.
@Nullable private final Method postEncodeMethod;
// The structural object class that should be used for entries created from
// objects of the associated type.
@NotNull private final String structuralClass;
// The set of attributes that should be requested when performing a search.
// It will not include lazily-loaded attributes.
@NotNull private final String[] attributesToRequest;
// The auxiliary object classes that should should used for entries created
// from objects of the associated type.
@NotNull private final String[] auxiliaryClasses;
// The set of attributes that will be requested if @LDAPObject has
// requestAllAttributes is false. Even if requestAllAttributes is true, this
// may be used if a subclass has requestAllAttributes set to false.
@NotNull private final String[] explicitAttributesToRequest;
// The set of attributes that should be lazily loaded.
@NotNull private final String[] lazilyLoadedAttributes;
// The superior object classes that should should used for entries created
// from objects of the associated type.
@NotNull private final String[] superiorClasses;
/**
* Creates a new instance of this handler that will handle objects of the
* specified type.
*
* @param type The type of object that will be handled by this class.
*
* @throws LDAPPersistException If there is a problem with the provided
* class that makes it unsuitable for use with
* the persistence framework.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
LDAPObjectHandler(@NotNull final Class type)
throws LDAPPersistException
{
this.type = type;
final Class super T> superclassType = type.getSuperclass();
if (superclassType == null)
{
superclassHandler = null;
}
else
{
final LDAPObject superclassAnnotation =
superclassType.getAnnotation(LDAPObject.class);
if (superclassAnnotation == null)
{
superclassHandler = null;
}
else
{
superclassHandler = new LDAPObjectHandler(superclassType);
}
}
final TreeMap fields = new TreeMap<>();
final TreeMap getters = new TreeMap<>();
final TreeMap setters = new TreeMap<>();
ldapObject = type.getAnnotation(LDAPObject.class);
if (ldapObject == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_OBJECT_NOT_ANNOTATED.get(type.getName()));
}
final LinkedHashMap objectClasses =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
final String oc = ldapObject.structuralClass();
if (oc.isEmpty())
{
structuralClass = StaticUtils.getUnqualifiedClassName(type);
}
else
{
structuralClass = oc;
}
final StringBuilder invalidReason = new StringBuilder();
if (PersistUtils.isValidLDAPName(structuralClass, invalidReason))
{
objectClasses.put(StaticUtils.toLowerCase(structuralClass),
structuralClass);
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_STRUCTURAL_CLASS.get(type.getName(),
structuralClass, invalidReason.toString()));
}
auxiliaryClasses = ldapObject.auxiliaryClass();
for (final String auxiliaryClass : auxiliaryClasses)
{
if (PersistUtils.isValidLDAPName(auxiliaryClass, invalidReason))
{
objectClasses.put(StaticUtils.toLowerCase(auxiliaryClass),
auxiliaryClass);
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_AUXILIARY_CLASS.get(type.getName(),
auxiliaryClass, invalidReason.toString()));
}
}
superiorClasses = ldapObject.superiorClass();
for (final String superiorClass : superiorClasses)
{
if (PersistUtils.isValidLDAPName(superiorClass, invalidReason))
{
objectClasses.put(StaticUtils.toLowerCase(superiorClass),
superiorClass);
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_SUPERIOR_CLASS.get(type.getName(),
superiorClass, invalidReason.toString()));
}
}
if (superclassHandler != null)
{
for (final String s : superclassHandler.objectClassAttribute.getValues())
{
objectClasses.put(StaticUtils.toLowerCase(s), s);
}
}
objectClassAttribute = new Attribute("objectClass", objectClasses.values());
final String parentDNStr = ldapObject.defaultParentDN();
try
{
if ((parentDNStr.isEmpty()) && (superclassHandler != null))
{
defaultParentDN = superclassHandler.getDefaultParentDN();
}
else
{
defaultParentDN = new DN(parentDNStr);
}
}
catch (final LDAPException le)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_DEFAULT_PARENT.get(type.getName(),
parentDNStr, le.getMessage()), le);
}
final String postDecodeMethodName = ldapObject.postDecodeMethod();
if (! postDecodeMethodName.isEmpty())
{
try
{
postDecodeMethod = type.getDeclaredMethod(postDecodeMethodName);
postDecodeMethod.setAccessible(true);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_POST_DECODE_METHOD.get(type.getName(),
postDecodeMethodName, StaticUtils.getExceptionMessage(e)),
e);
}
}
else
{
postDecodeMethod = null;
}
final String postEncodeMethodName = ldapObject.postEncodeMethod();
if (! postEncodeMethodName.isEmpty())
{
try
{
postEncodeMethod = type.getDeclaredMethod(postEncodeMethodName,
Entry.class);
postEncodeMethod.setAccessible(true);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_POST_ENCODE_METHOD.get(type.getName(),
postEncodeMethodName, StaticUtils.getExceptionMessage(e)),
e);
}
}
else
{
postEncodeMethod = null;
}
try
{
constructor = type.getDeclaredConstructor();
constructor.setAccessible(true);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_NO_DEFAULT_CONSTRUCTOR.get(type.getName()), e);
}
Field tmpDNField = null;
Field tmpEntryField = null;
final LinkedList tmpRFilterFields = new LinkedList<>();
final LinkedList tmpAAFilterFields = new LinkedList<>();
final LinkedList tmpCAFilterFields = new LinkedList<>();
final LinkedList tmpRDNFields = new LinkedList<>();
for (final Field f : type.getDeclaredFields())
{
final LDAPField fieldAnnotation = f.getAnnotation(LDAPField.class);
final LDAPDNField dnFieldAnnotation = f.getAnnotation(LDAPDNField.class);
final LDAPEntryField entryFieldAnnotation =
f.getAnnotation(LDAPEntryField.class);
if (fieldAnnotation != null)
{
f.setAccessible(true);
final FieldInfo fieldInfo = new FieldInfo(f, type);
final String attrName =
StaticUtils.toLowerCase(fieldInfo.getAttributeName());
if (fields.containsKey(attrName))
{
throw new LDAPPersistException(ERR_OBJECT_HANDLER_ATTR_CONFLICT.get(
type.getName(), fieldInfo.getAttributeName()));
}
else
{
fields.put(attrName, fieldInfo);
}
switch (fieldInfo.getFilterUsage())
{
case REQUIRED:
tmpRFilterFields.add(fieldInfo);
break;
case ALWAYS_ALLOWED:
tmpAAFilterFields.add(fieldInfo);
break;
case CONDITIONALLY_ALLOWED:
tmpCAFilterFields.add(fieldInfo);
break;
case EXCLUDED:
default:
// No action required.
break;
}
if (fieldInfo.includeInRDN())
{
tmpRDNFields.add(fieldInfo);
}
}
if (dnFieldAnnotation != null)
{
f.setAccessible(true);
if (fieldAnnotation != null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_CONFLICTING_FIELD_ANNOTATIONS.get(
type.getName(), "LDAPField", "LDAPDNField", f.getName()));
}
if (tmpDNField != null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_MULTIPLE_DN_FIELDS.get(type.getName()));
}
final int modifiers = f.getModifiers();
if (Modifier.isFinal(modifiers))
{
throw new LDAPPersistException(ERR_OBJECT_HANDLER_DN_FIELD_FINAL.get(
f.getName(), type.getName()));
}
else if (Modifier.isStatic(modifiers))
{
throw new LDAPPersistException(ERR_OBJECT_HANDLER_DN_FIELD_STATIC.get(
f.getName(), type.getName()));
}
final Class> fieldType = f.getType();
if (fieldType.equals(String.class))
{
tmpDNField = f;
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_DN_FIELD_TYPE.get(type.getName(),
f.getName(), fieldType.getName()));
}
}
if (entryFieldAnnotation != null)
{
f.setAccessible(true);
if (fieldAnnotation != null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_CONFLICTING_FIELD_ANNOTATIONS.get(
type.getName(), "LDAPField", "LDAPEntryField",
f.getName()));
}
if (tmpEntryField != null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_MULTIPLE_ENTRY_FIELDS.get(type.getName()));
}
final int modifiers = f.getModifiers();
if (Modifier.isFinal(modifiers))
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ENTRY_FIELD_FINAL.get(f.getName(),
type.getName()));
}
else if (Modifier.isStatic(modifiers))
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ENTRY_FIELD_STATIC.get(f.getName(),
type.getName()));
}
final Class> fieldType = f.getType();
if (fieldType.equals(ReadOnlyEntry.class))
{
tmpEntryField = f;
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_INVALID_ENTRY_FIELD_TYPE.get(type.getName(),
f.getName(), fieldType.getName()));
}
}
}
dnField = tmpDNField;
entryField = tmpEntryField;
requiredFilterFields = Collections.unmodifiableList(tmpRFilterFields);
alwaysAllowedFilterFields = Collections.unmodifiableList(tmpAAFilterFields);
conditionallyAllowedFilterFields =
Collections.unmodifiableList(tmpCAFilterFields);
rdnFields = Collections.unmodifiableList(tmpRDNFields);
final LinkedList tmpRFilterGetters = new LinkedList<>();
final LinkedList tmpAAFilterGetters = new LinkedList<>();
final LinkedList tmpCAFilterGetters = new LinkedList<>();
final LinkedList tmpRDNGetters = new LinkedList<>();
for (final Method m : type.getDeclaredMethods())
{
final LDAPGetter getter = m.getAnnotation(LDAPGetter.class);
final LDAPSetter setter = m.getAnnotation(LDAPSetter.class);
if (getter != null)
{
m.setAccessible(true);
if (setter != null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_CONFLICTING_METHOD_ANNOTATIONS.get(
type.getName(), "LDAPGetter", "LDAPSetter",
m.getName()));
}
final GetterInfo methodInfo = new GetterInfo(m, type);
final String attrName =
StaticUtils.toLowerCase(methodInfo.getAttributeName());
if (fields.containsKey(attrName) || getters.containsKey(attrName))
{
throw new LDAPPersistException(ERR_OBJECT_HANDLER_ATTR_CONFLICT.get(
type.getName(), methodInfo.getAttributeName()));
}
else
{
getters.put(attrName, methodInfo);
}
switch (methodInfo.getFilterUsage())
{
case REQUIRED:
tmpRFilterGetters.add(methodInfo);
break;
case ALWAYS_ALLOWED:
tmpAAFilterGetters.add(methodInfo);
break;
case CONDITIONALLY_ALLOWED:
tmpCAFilterGetters.add(methodInfo);
break;
case EXCLUDED:
default:
// No action required.
break;
}
if (methodInfo.includeInRDN())
{
tmpRDNGetters.add(methodInfo);
}
}
if (setter != null)
{
m.setAccessible(true);
final SetterInfo methodInfo = new SetterInfo(m, type);
final String attrName =
StaticUtils.toLowerCase(methodInfo.getAttributeName());
if (fields.containsKey(attrName) || setters.containsKey(attrName))
{
throw new LDAPPersistException(ERR_OBJECT_HANDLER_ATTR_CONFLICT.get(
type.getName(), methodInfo.getAttributeName()));
}
else
{
setters.put(attrName, methodInfo);
}
}
}
requiredFilterGetters = Collections.unmodifiableList(tmpRFilterGetters);
alwaysAllowedFilterGetters =
Collections.unmodifiableList(tmpAAFilterGetters);
conditionallyAllowedFilterGetters =
Collections.unmodifiableList(tmpCAFilterGetters);
rdnGetters = Collections.unmodifiableList(tmpRDNGetters);
if (rdnFields.isEmpty() && rdnGetters.isEmpty() &&
(superclassHandler == null))
{
throw new LDAPPersistException(ERR_OBJECT_HANDLER_NO_RDN_DEFINED.get(
type.getName()));
}
fieldMap = Collections.unmodifiableMap(fields);
getterMap = Collections.unmodifiableMap(getters);
setterMap = Collections.unmodifiableMap(setters);
final TreeSet attrSet = new TreeSet<>();
final TreeSet lazySet = new TreeSet<>();
for (final FieldInfo i : fields.values())
{
if (i.lazilyLoad())
{
lazySet.add(i.getAttributeName());
}
else
{
attrSet.add(i.getAttributeName());
}
}
for (final SetterInfo i : setters.values())
{
attrSet.add(i.getAttributeName());
}
if (superclassHandler != null)
{
attrSet.addAll(Arrays.asList(
superclassHandler.explicitAttributesToRequest));
lazySet.addAll(Arrays.asList(superclassHandler.lazilyLoadedAttributes));
}
explicitAttributesToRequest = new String[attrSet.size()];
attrSet.toArray(explicitAttributesToRequest);
if (requestAllAttributes())
{
attributesToRequest = new String[] { "*", "+" };
}
else
{
attributesToRequest = explicitAttributesToRequest;
}
lazilyLoadedAttributes = new String[lazySet.size()];
lazySet.toArray(lazilyLoadedAttributes);
}
/**
* Retrieves the type of object handled by this class.
*
* @return The type of object handled by this class.
*/
@NotNull()
public Class getType()
{
return type;
}
/**
* Retrieves the {@code LDAPObjectHandler} object for the superclass of the
* associated type, if it is marked with the {@code LDAPObject annotation}.
*
* @return The {@code LDAPObjectHandler} object for the superclass of the
* associated type, or {@code null} if the superclass is not marked
* with the {@code LDAPObject} annotation.
*/
@Nullable()
public LDAPObjectHandler> getSuperclassHandler()
{
return superclassHandler;
}
/**
* Retrieves the {@link LDAPObject} annotation for the associated class.
*
* @return The {@code LDAPObject} annotation for the associated class.
*/
@NotNull()
public LDAPObject getLDAPObjectAnnotation()
{
return ldapObject;
}
/**
* Retrieves the constructor used to create a new instance of the appropriate
* type.
*
* @return The constructor used to create a new instance of the appropriate
* type.
*/
@NotNull()
public Constructor getConstructor()
{
return constructor;
}
/**
* Retrieves the field that will be used to hold the DN of the associated
* entry, if defined.
*
* @return The field that will be used to hold the DN of the associated
* entry, or {@code null} if no DN field is defined in the associated
* object type.
*/
@Nullable()
public Field getDNField()
{
return dnField;
}
/**
* Retrieves the field that will be used to hold a read-only copy of the entry
* used to create the object instance, if defined.
*
* @return The field that will be used to hold a read-only copy of the entry
* used to create the object instance, or {@code null} if no entry
* field is defined in the associated object type.
*/
@Nullable()
public Field getEntryField()
{
return entryField;
}
/**
* Retrieves the default parent DN for objects of the associated type.
*
* @return The default parent DN for objects of the associated type.
*/
@NotNull()
public DN getDefaultParentDN()
{
return defaultParentDN;
}
/**
* Retrieves the name of the structural object class for objects of the
* associated type.
*
* @return The name of the structural object class for objects of the
* associated type.
*/
@NotNull()
public String getStructuralClass()
{
return structuralClass;
}
/**
* Retrieves the names of the auxiliary object classes for objects of the
* associated type.
*
* @return The names of the auxiliary object classes for objects of the
* associated type. It may be empty if no auxiliary classes are
* defined.
*/
@NotNull()
public String[] getAuxiliaryClasses()
{
return auxiliaryClasses;
}
/**
* Retrieves the names of the superior object classes for objects of the
* associated type.
*
* @return The names of the superior object classes for objects of the
* associated type. It may be empty if no superior classes are
* defined.
*/
@NotNull()
public String[] getSuperiorClasses()
{
return superiorClasses;
}
/**
* Indicates whether to request all attributes. This will return {@code true}
* if the associated {@code LDAPObject}, or any {@code LDAPObject} for any
* superclass, has {@code requestAllAttributes} set to {@code true}.
*
* @return {@code true} if {@code LDAPObject} has
* {@code requestAllAttributes} set to {@code true} for any class in
* the hierarchy, or {@code false} if not.
*/
public boolean requestAllAttributes()
{
return (ldapObject.requestAllAttributes() ||
((superclassHandler != null) &&
superclassHandler.requestAllAttributes()));
}
/**
* Retrieves the names of the attributes that should be requested when
* performing a search. It will not include lazily-loaded attributes.
*
* @return The names of the attributes that should be requested when
* performing a search.
*/
@NotNull()
public String[] getAttributesToRequest()
{
return attributesToRequest;
}
/**
* Retrieves the names of the attributes that should be lazily loaded for
* objects of this type.
*
* @return The names of the attributes that should be lazily loaded for
* objects of this type. It may be empty if no attributes should be
* lazily-loaded.
*/
@NotNull()
public String[] getLazilyLoadedAttributes()
{
return lazilyLoadedAttributes;
}
/**
* Retrieves the DN of the entry in which the provided object is stored, if
* available. The entry DN will not be available if the provided object was
* not retrieved using the persistence framework, or if the associated class
* (or one of its superclasses) does not have a field marked with either the
* {@link LDAPDNField} or {@link LDAPEntryField} annotation.
*
* @param o The object for which to retrieve the associated entry DN.
*
* @return The DN of the entry in which the provided object is stored, or
* {@code null} if that is not available.
*
* @throws LDAPPersistException If a problem occurred while attempting to
* obtain the entry DN.
*/
@Nullable()
public String getEntryDN(@NotNull final T o)
throws LDAPPersistException
{
final String dnFieldValue = getDNFieldValue(o);
if (dnFieldValue != null)
{
return dnFieldValue;
}
final ReadOnlyEntry entry = getEntry(o);
if (entry != null)
{
return entry.getDN();
}
return null;
}
/**
* Retrieves the value of the DN field for the provided object. If there is
* no DN field in this object handler but there is one defined for a handler
* for one of its superclasses, then it will be obtained recursively.
*
* @param o The object for which to retrieve the associated entry DN.
*
* @return The value of the DN field for the provided object.
*
* @throws LDAPPersistException If a problem is encountered while attempting
* to access the value of the DN field.
*/
@Nullable()
private String getDNFieldValue(@NotNull final T o)
throws LDAPPersistException
{
if (dnField != null)
{
try
{
final Object dnObject = dnField.get(o);
if (dnObject == null)
{
return null;
}
else
{
return String.valueOf(dnObject);
}
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_ACCESSING_DN_FIELD.get(dnField.getName(),
type.getName(), StaticUtils.getExceptionMessage(e)),
e);
}
}
if (superclassHandler != null)
{
return superclassHandler.getDNFieldValue(o);
}
return null;
}
/**
* Retrieves a read-only copy of the entry that was used to initialize the
* provided object, if available. The entry will only be available if the
* object was retrieved from the directory using the persistence framework and
* the associated class (or one of its superclasses) has a field marked with
* the {@link LDAPEntryField} annotation.
*
* @param o The object for which to retrieve the read-only entry.
*
* @return A read-only copy of the entry that was used to initialize the
* provided object, or {@code null} if that is not available.
*
* @throws LDAPPersistException If a problem occurred while attempting to
* obtain the entry DN.
*/
@Nullable()
public ReadOnlyEntry getEntry(@NotNull final T o)
throws LDAPPersistException
{
if (entryField != null)
{
try
{
final Object entryObject = entryField.get(o);
if (entryObject == null)
{
return null;
}
else
{
return (ReadOnlyEntry) entryObject;
}
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_ACCESSING_ENTRY_FIELD.get(
entryField.getName(), type.getName(),
StaticUtils.getExceptionMessage(e)),
e);
}
}
if (superclassHandler != null)
{
return superclassHandler.getEntry(o);
}
return null;
}
/**
* Retrieves a map of all fields in the class that should be persisted as LDAP
* attributes. The keys in the map will be the lowercase names of the LDAP
* attributes used to persist the information, and the values will be
* information about the fields associated with those attributes.
*
* @return A map of all fields in the class that should be persisted as LDAP
* attributes.
*/
@NotNull()
public Map getFields()
{
return fieldMap;
}
/**
* Retrieves a map of all getter methods in the class whose values should be
* persisted as LDAP attributes. The keys in the map will be the lowercase
* names of the LDAP attributes used to persist the information, and the
* values will be information about the getter methods associated with those
* attributes.
*
* @return A map of all getter methods in the class whose values should be
* persisted as LDAP attributes.
*/
@NotNull()
public Map getGetters()
{
return getterMap;
}
/**
* Retrieves a map of all setter methods in the class that should be invoked
* with information read from LDAP attributes. The keys in the map will be
* the lowercase names of the LDAP attributes with the information used to
* invoke the setter, and the values will be information about the setter
* methods associated with those attributes.
*
* @return A map of all setter methods in the class that should be invoked
* with information read from LDAP attributes.
*/
@NotNull()
public Map getSetters()
{
return setterMap;
}
/**
* Constructs a list of LDAP object class definitions which may be added to
* the directory server schema to allow it to hold objects of this type. Note
* that the object identifiers used for the constructed object class
* definitions are not required to be valid or unique.
*
* @param a The OID allocator to use to generate the object identifiers for
* the constructed attribute types. It must not be {@code null}.
*
* @return A list of object class definitions that may be used to represent
* objects of the associated type in an LDAP directory.
*
* @throws LDAPPersistException If a problem occurs while attempting to
* generate the list of object class
* definitions.
*/
@NotNull()
List constructObjectClasses(
@NotNull final OIDAllocator a)
throws LDAPPersistException
{
final LinkedHashMap ocMap =
new LinkedHashMap<>(
StaticUtils.computeMapCapacity(1 + auxiliaryClasses.length));
if (superclassHandler != null)
{
for (final ObjectClassDefinition d :
superclassHandler.constructObjectClasses(a))
{
ocMap.put(StaticUtils.toLowerCase(d.getNameOrOID()), d);
}
}
final String lowerStructuralClass =
StaticUtils.toLowerCase(structuralClass);
if (! ocMap.containsKey(lowerStructuralClass))
{
if (superclassHandler == null)
{
ocMap.put(lowerStructuralClass, constructObjectClass(structuralClass,
"top", ObjectClassType.STRUCTURAL, a));
}
else
{
ocMap.put(lowerStructuralClass, constructObjectClass(structuralClass,
superclassHandler.getStructuralClass(), ObjectClassType.STRUCTURAL,
a));
}
}
for (final String s : auxiliaryClasses)
{
final String lowerName = StaticUtils.toLowerCase(s);
if (! ocMap.containsKey(lowerName))
{
ocMap.put(lowerName,
constructObjectClass(s, "top", ObjectClassType.AUXILIARY, a));
}
}
return Collections.unmodifiableList(new ArrayList<>(ocMap.values()));
}
/**
* Constructs an LDAP object class definition for the object class with the
* specified name.
*
* @param name The name of the object class to create. It must not be
* {@code null}.
* @param sup The name of the superior object class. It must not be
* {@code null}.
* @param type The type of object class to create. It must not be
* {@code null}.
* @param a The OID allocator to use to generate the object identifiers
* for the constructed attribute types. It must not be
* {@code null}.
*
* @return The constructed object class definition.
*/
@NotNull()
private ObjectClassDefinition constructObjectClass(@NotNull final String name,
@NotNull final String sup,
@NotNull final ObjectClassType type,
@NotNull final OIDAllocator a)
{
final TreeMap requiredAttrs = new TreeMap<>();
final TreeMap optionalAttrs = new TreeMap<>();
// Extract the attributes for all of the fields.
for (final FieldInfo i : fieldMap.values())
{
boolean found = false;
for (final String s : i.getObjectClasses())
{
if (name.equalsIgnoreCase(s))
{
found = true;
break;
}
}
if (! found)
{
continue;
}
final String attrName = i.getAttributeName();
final String lowerName = StaticUtils.toLowerCase(attrName);
if (i.includeInRDN() ||
(i.isRequiredForDecode() && i.isRequiredForEncode()))
{
requiredAttrs.put(lowerName, attrName);
}
else
{
optionalAttrs.put(lowerName, attrName);
}
}
// Extract the attributes for all of the getter methods.
for (final GetterInfo i : getterMap.values())
{
boolean found = false;
for (final String s : i.getObjectClasses())
{
if (name.equalsIgnoreCase(s))
{
found = true;
break;
}
}
if (! found)
{
continue;
}
final String attrName = i.getAttributeName();
final String lowerName = StaticUtils.toLowerCase(attrName);
if (i.includeInRDN())
{
requiredAttrs.put(lowerName, attrName);
}
else
{
optionalAttrs.put(lowerName, attrName);
}
}
// Extract the attributes for all of the setter methods. We'll assume that
// they are all part of the structural object class and all optional.
if (name.equalsIgnoreCase(structuralClass))
{
for (final SetterInfo i : setterMap.values())
{
final String attrName = i.getAttributeName();
final String lowerName = StaticUtils.toLowerCase(attrName);
if (requiredAttrs.containsKey(lowerName) ||
optionalAttrs.containsKey(lowerName))
{
continue;
}
optionalAttrs.put(lowerName, attrName);
}
}
final String[] reqArray = new String[requiredAttrs.size()];
requiredAttrs.values().toArray(reqArray);
final String[] optArray = new String[optionalAttrs.size()];
optionalAttrs.values().toArray(optArray);
return new ObjectClassDefinition(a.allocateObjectClassOID(name),
new String[] { name }, null, false, new String[] { sup }, type,
reqArray, optArray, null);
}
/**
* Creates a new object based on the contents of the provided entry.
*
* @param e The entry to use to create and initialize the object.
*
* @return The object created from the provided entry.
*
* @throws LDAPPersistException If an error occurs while creating or
* initializing the object from the information
* in the provided entry.
*/
@NotNull()
T decode(@NotNull final Entry e)
throws LDAPPersistException
{
final T o;
try
{
o = constructor.newInstance();
}
catch (final Exception ex)
{
Debug.debugException(ex);
if (ex instanceof InvocationTargetException)
{
final Throwable targetException =
((InvocationTargetException) ex).getTargetException();
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_INVOKING_CONSTRUCTOR.get(type.getName(),
StaticUtils.getExceptionMessage(targetException)),
targetException);
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_INVOKING_CONSTRUCTOR.get(type.getName(),
StaticUtils.getExceptionMessage(ex)),
ex);
}
}
decode(o, e);
return o;
}
/**
* Initializes the provided object from the contents of the provided entry.
*
* @param o The object to be initialized with the contents of the provided
* entry.
* @param e The entry to use to initialize the object.
*
* @throws LDAPPersistException If an error occurs while initializing the
* object from the information in the provided
* entry.
*/
void decode(@NotNull final T o, @NotNull final Entry e)
throws LDAPPersistException
{
if (superclassHandler != null)
{
superclassHandler.decode(o, e);
}
setDNAndEntryFields(o, e);
final ArrayList failureReasons = new ArrayList<>(5);
boolean successful = true;
for (final FieldInfo i : fieldMap.values())
{
successful &= i.decode(o, e, failureReasons);
}
for (final SetterInfo i : setterMap.values())
{
successful &= i.invokeSetter(o, e, failureReasons);
}
Throwable cause = null;
if (postDecodeMethod != null)
{
try
{
postDecodeMethod.invoke(o);
}
catch (final Exception ex)
{
Debug.debugException(ex);
StaticUtils.rethrowIfError(ex);
if (ex instanceof InvocationTargetException)
{
cause = ((InvocationTargetException) ex).getTargetException();
}
else
{
cause = ex;
}
successful = false;
failureReasons.add(
ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_DECODE_METHOD.get(
postDecodeMethod.getName(), type.getName(),
StaticUtils.getExceptionMessage(ex)));
}
}
if (! successful)
{
throw new LDAPPersistException(
StaticUtils.concatenateStrings(failureReasons), o, cause);
}
}
/**
* Encodes the provided object to an entry suitable for use in an add
* operation.
*
* @param o The object to be encoded.
* @param parentDN The parent DN to use by default for the entry that is
* generated. If the provided object was previously read
* from a directory server and includes a DN field or an
* entry field with the original DN used for the object,
* then that original DN will be used even if it is not
* an immediate subordinate of the provided parent. This
* may be {@code null} if the entry to create should not
* have a parent but instead should have a DN consisting of
* only a single RDN component.
*
* @return The entry containing an encoded representation of the provided
* object.
*
* @throws LDAPPersistException If a problem occurs while encoding the
* provided object.
*/
@NotNull()
Entry encode(@NotNull final T o, @Nullable final String parentDN)
throws LDAPPersistException
{
// Get the attributes that should be included in the entry.
final LinkedHashMap attrMap =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
attrMap.put("objectClass", objectClassAttribute);
for (final Map.Entry e : fieldMap.entrySet())
{
final FieldInfo i = e.getValue();
if (! i.includeInAdd())
{
continue;
}
final Attribute a = i.encode(o, false);
if (a != null)
{
attrMap.put(e.getKey(), a);
}
}
for (final Map.Entry e : getterMap.entrySet())
{
final GetterInfo i = e.getValue();
if (! i.includeInAdd())
{
continue;
}
final Attribute a = i.encode(o);
if (a != null)
{
attrMap.put(e.getKey(), a);
}
}
// Get the DN to use for the entry.
final String dn = constructDN(o, parentDN, attrMap);
final Entry entry = new Entry(dn, attrMap.values());
if (postEncodeMethod != null)
{
try
{
postEncodeMethod.invoke(o, entry);
}
catch (final Exception ex)
{
Debug.debugException(ex);
if (ex instanceof InvocationTargetException)
{
final Throwable targetException =
((InvocationTargetException) ex).getTargetException();
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_ENCODE_METHOD.get(
postEncodeMethod.getName(), type.getName(),
StaticUtils.getExceptionMessage(targetException)),
targetException);
}
else
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_ENCODE_METHOD.get(
postEncodeMethod.getName(), type.getName(),
StaticUtils.getExceptionMessage(ex)), ex);
}
}
}
setDNAndEntryFields(o, entry);
if (superclassHandler != null)
{
final Entry e = superclassHandler.encode(o, parentDN);
for (final Attribute a : e.getAttributes())
{
entry.addAttribute(a);
}
}
return entry;
}
/**
* Sets the DN and entry fields for the provided object, if appropriate.
*
* @param o The object to be updated.
* @param e The entry with which the object is associated.
*
* @throws LDAPPersistException If a problem occurs while setting the value
* of the DN or entry field.
*/
private void setDNAndEntryFields(@NotNull final T o, @NotNull final Entry e)
throws LDAPPersistException
{
if (dnField != null)
{
try
{
if (dnField.get(o) == null)
{
dnField.set(o, e.getDN());
}
}
catch (final Exception ex)
{
Debug.debugException(ex);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_SETTING_DN.get(type.getName(), e.getDN(),
dnField.getName(), StaticUtils.getExceptionMessage(ex)),
ex);
}
}
if (entryField != null)
{
try
{
if (entryField.get(o) == null)
{
entryField.set(o, new ReadOnlyEntry(e));
}
}
catch (final Exception ex)
{
Debug.debugException(ex);
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_ERROR_SETTING_ENTRY.get(type.getName(),
entryField.getName(), StaticUtils.getExceptionMessage(ex)),
ex);
}
}
if (superclassHandler != null)
{
superclassHandler.setDNAndEntryFields(o, e);
}
}
/**
* Determines the DN that should be used for the entry associated with the
* given object. If the provided object was retrieved from the directory
* using the persistence framework and has a field with either the
* {@link LDAPDNField} or {@link LDAPEntryField} annotation, then the actual
* DN of the corresponding entry will be returned. Otherwise, it will be
* constructed using the fields and getter methods marked for inclusion in
* the entry RDN.
*
* @param o The object for which to determine the appropriate DN.
* @param parentDN The parent DN to use for the constructed DN. If a
* non-{@code null} value is provided, then that value will
* be used as the parent DN (and the empty string will
* indicate that the generated DN should not have a parent).
* If the value is {@code null}, then the default parent DN
* as defined in the {@link LDAPObject} annotation will be
* used. If the provided parent DN is {@code null} and the
* {@code LDAPObject} annotation does not specify a default
* parent DN, then the generated DN will not have a parent.
*
* @return The entry DN for the provided object.
*
* @throws LDAPPersistException If a problem occurs while obtaining the
* entry DN, or if the provided parent DN
* represents an invalid DN.
*/
@NotNull()
public String constructDN(@NotNull final T o, @Nullable final String parentDN)
throws LDAPPersistException
{
final String existingDN = getEntryDN(o);
if (existingDN != null)
{
return existingDN;
}
final int numRDNs = rdnFields.size() + rdnGetters.size();
if (numRDNs == 0)
{
return superclassHandler.constructDN(o, parentDN);
}
final LinkedHashMap attrMap =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(numRDNs));
for (final FieldInfo i : rdnFields)
{
final Attribute a = i.encode(o, true);
if (a == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_RDN_FIELD_MISSING_VALUE.get(type.getName(),
i.getField().getName()));
}
attrMap.put(StaticUtils.toLowerCase(i.getAttributeName()), a);
}
for (final GetterInfo i : rdnGetters)
{
final Attribute a = i.encode(o);
if (a == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_RDN_GETTER_MISSING_VALUE.get(type.getName(),
i.getMethod().getName()));
}
attrMap.put(StaticUtils.toLowerCase(i.getAttributeName()), a);
}
return constructDN(o, parentDN, attrMap);
}
/**
* Determines the DN that should be used for the entry associated with the
* given object. If the provided object was retrieved from the directory
* using the persistence framework and has a field with either the
* {@link LDAPDNField} or {@link LDAPEntryField} annotation, then the actual
* DN of the corresponding entry will be returned. Otherwise, it will be
* constructed using the fields and getter methods marked for inclusion in
* the entry RDN.
*
* @param o The object for which to determine the appropriate DN.
* @param parentDN The parent DN to use for the constructed DN. If a
* non-{@code null} value is provided, then that value will
* be used as the parent DN (and the empty string will
* indicate that the generated DN should not have a parent).
* If the value is {@code null}, then the default parent DN
* as defined in the {@link LDAPObject} annotation will be
* used. If the provided parent DN is {@code null} and the
* {@code LDAPObject} annotation does not specify a default
* parent DN, then the generated DN will not have a parent.
* @param attrMap A map of the attributes that will be included in the
* entry and may be used to construct the RDN elements.
*
* @return The entry DN for the provided object.
*
* @throws LDAPPersistException If a problem occurs while obtaining the
* entry DN, or if the provided parent DN
* represents an invalid DN.
*/
@NotNull()
String constructDN(@NotNull final T o, @Nullable final String parentDN,
@NotNull final Map attrMap)
throws LDAPPersistException
{
final String existingDN = getEntryDN(o);
if (existingDN != null)
{
return existingDN;
}
final int numRDNs = rdnFields.size() + rdnGetters.size();
if (numRDNs == 0)
{
return superclassHandler.constructDN(o, parentDN);
}
final ArrayList rdnNameList = new ArrayList<>(numRDNs);
final ArrayList rdnValueList = new ArrayList<>(numRDNs);
for (final FieldInfo i : rdnFields)
{
final Attribute a =
attrMap.get(StaticUtils.toLowerCase(i.getAttributeName()));
if (a == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_RDN_FIELD_MISSING_VALUE.get(type.getName(),
i.getField().getName()));
}
rdnNameList.add(a.getName());
rdnValueList.add(a.getValueByteArray());
}
for (final GetterInfo i : rdnGetters)
{
final Attribute a =
attrMap.get(StaticUtils.toLowerCase(i.getAttributeName()));
if (a == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_RDN_GETTER_MISSING_VALUE.get(type.getName(),
i.getMethod().getName()));
}
rdnNameList.add(a.getName());
rdnValueList.add(a.getValueByteArray());
}
final String[] rdnNames = new String[rdnNameList.size()];
rdnNameList.toArray(rdnNames);
final byte[][] rdnValues = new byte[rdnNames.length][];
rdnValueList.toArray(rdnValues);
final RDN rdn = new RDN(rdnNames, rdnValues);
if (parentDN == null)
{
return new DN(rdn, defaultParentDN).toString();
}
else
{
try
{
final DN parsedParentDN = new DN(parentDN);
return new DN(rdn, parsedParentDN).toString();
}
catch (final LDAPException le)
{
Debug.debugException(le);
throw new LDAPPersistException(ERR_OBJECT_HANDLER_INVALID_PARENT_DN.get(
type.getName(), parentDN, le.getMessage()), le);
}
}
}
/**
* Creates a list of modifications that can be used to update the stored
* representation of the provided object in the directory. If the provided
* object was retrieved from the directory using the persistence framework and
* includes a field with the {@link LDAPEntryField} annotation, then that
* entry will be used to make the returned set of modifications as efficient
* as possible. Otherwise, the resulting modifications will include attempts
* to replace every attribute which are associated with fields or getters
* that should be used in modify operations.
*
* @param o The object to be encoded.
* @param deleteNullValues Indicates whether to include modifications that
* may completely remove an attribute from the
* entry if the corresponding field or getter method
* has a value of {@code null}.
* @param byteForByte Indicates whether to use a byte-for-byte
* comparison to identify which attribute values
* have changed. Using byte-for-byte comparison
* requires additional processing over using each
* attribute's associated matching rule, but it can
* detect changes that would otherwise be considered
* logically equivalent (e.g., changing the
* capitalization of a value that uses a
* case-insensitive matching rule).
* @param attributes The set of LDAP attributes for which to include
* modifications. If this is empty or {@code null},
* then all attributes marked for inclusion in the
* modification will be examined.
*
* @return A list of modifications that can be used to update the stored
* representation of the provided object in the directory. It may
* be empty if there are no differences identified in the attributes
* to be evaluated.
*
* @throws LDAPPersistException If a problem occurs while computing the set
* of modifications.
*/
@NotNull()
List getModifications(@NotNull final T o,
final boolean deleteNullValues,
final boolean byteForByte,
@Nullable final String... attributes)
throws LDAPPersistException
{
final ReadOnlyEntry originalEntry;
if (entryField != null)
{
originalEntry = getEntry(o);
}
else
{
originalEntry = null;
}
// If we have an original copy of the entry, then we can try encoding the
// updated object to a new entry and diff the two entries.
if (originalEntry != null)
{
try
{
final T decodedOrig = decode(originalEntry);
final Entry reEncodedOriginal =
encode(decodedOrig, originalEntry.getParentDNString());
final Entry newEntry = encode(o, originalEntry.getParentDNString());
final List mods = Entry.diff(reEncodedOriginal, newEntry,
true, false, byteForByte, attributes);
if (! deleteNullValues)
{
final Iterator iterator = mods.iterator();
while (iterator.hasNext())
{
final Modification m = iterator.next();
if (m.getRawValues().length == 0)
{
iterator.remove();
}
}
}
// If there are any attributes that should be excluded from
// modifications, then strip them out.
HashSet stripAttrs = null;
for (final FieldInfo i : fieldMap.values())
{
if (! i.includeInModify())
{
if (stripAttrs == null)
{
stripAttrs = new HashSet<>(StaticUtils.computeMapCapacity(10));
}
stripAttrs.add(StaticUtils.toLowerCase(i.getAttributeName()));
}
}
for (final GetterInfo i : getterMap.values())
{
if (! i.includeInModify())
{
if (stripAttrs == null)
{
stripAttrs = new HashSet<>(StaticUtils.computeMapCapacity(10));
}
stripAttrs.add(StaticUtils.toLowerCase(i.getAttributeName()));
}
}
if (stripAttrs != null)
{
final Iterator iterator = mods.iterator();
while (iterator.hasNext())
{
final Modification m = iterator.next();
if (stripAttrs.contains(
StaticUtils.toLowerCase(m.getAttributeName())))
{
iterator.remove();
}
}
}
return mods;
}
catch (final Exception e)
{
Debug.debugException(e);
}
finally
{
setDNAndEntryFields(o, originalEntry);
}
}
final HashSet attrSet;
if ((attributes == null) || (attributes.length == 0))
{
attrSet = null;
}
else
{
attrSet =
new HashSet<>(StaticUtils.computeMapCapacity(attributes.length));
for (final String s : attributes)
{
attrSet.add(StaticUtils.toLowerCase(s));
}
}
final ArrayList mods = new ArrayList<>(5);
for (final Map.Entry e : fieldMap.entrySet())
{
final String attrName = StaticUtils.toLowerCase(e.getKey());
if ((attrSet != null) && (! attrSet.contains(attrName)))
{
continue;
}
final FieldInfo i = e.getValue();
if (! i.includeInModify())
{
continue;
}
final Attribute a = i.encode(o, false);
if (a == null)
{
if (! deleteNullValues)
{
continue;
}
if ((originalEntry != null) && (! originalEntry.hasAttribute(attrName)))
{
continue;
}
mods.add(new Modification(ModificationType.REPLACE,
i.getAttributeName()));
continue;
}
if (originalEntry != null)
{
final Attribute originalAttr = originalEntry.getAttribute(attrName);
if ((originalAttr != null) && originalAttr.equals(a))
{
continue;
}
}
mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(),
a.getRawValues()));
}
for (final Map.Entry e : getterMap.entrySet())
{
final String attrName = StaticUtils.toLowerCase(e.getKey());
if ((attrSet != null) && (! attrSet.contains(attrName)))
{
continue;
}
final GetterInfo i = e.getValue();
if (! i.includeInModify())
{
continue;
}
final Attribute a = i.encode(o);
if (a == null)
{
if (! deleteNullValues)
{
continue;
}
if ((originalEntry != null) && (! originalEntry.hasAttribute(attrName)))
{
continue;
}
mods.add(new Modification(ModificationType.REPLACE,
i.getAttributeName()));
continue;
}
if (originalEntry != null)
{
final Attribute originalAttr = originalEntry.getAttribute(attrName);
if ((originalAttr != null) && originalAttr.equals(a))
{
continue;
}
}
mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(),
a.getRawValues()));
}
if (superclassHandler != null)
{
final List superMods =
superclassHandler.getModifications(o, deleteNullValues, byteForByte,
attributes);
final ArrayList modsToAdd =
new ArrayList<>(superMods.size());
for (final Modification sm : superMods)
{
boolean add = true;
for (final Modification m : mods)
{
if (m.getAttributeName().equalsIgnoreCase(sm.getAttributeName()))
{
add = false;
break;
}
}
if (add)
{
modsToAdd.add(sm);
}
}
mods.addAll(modsToAdd);
}
return Collections.unmodifiableList(mods);
}
/**
* Retrieves a filter that will match any entry containing the structural and
* auxiliary classes for this object type.
*
* @return A filter that will match any entry containing the structural and
* auxiliary classes for this object type.
*/
@NotNull()
public Filter createBaseFilter()
{
if (auxiliaryClasses.length == 0)
{
return Filter.createEqualityFilter("objectClass", structuralClass);
}
else
{
final ArrayList comps =
new ArrayList<>(1+auxiliaryClasses.length);
comps.add(Filter.createEqualityFilter("objectClass", structuralClass));
for (final String s : auxiliaryClasses)
{
comps.add(Filter.createEqualityFilter("objectClass", s));
}
return Filter.createANDFilter(comps);
}
}
/**
* Retrieves a filter that can be used to search for entries matching the
* provided object. It will be constructed as an AND search using all fields
* with a non-{@code null} value and that have a {@link LDAPField} annotation
* with the {@code inFilter} element set to {@code true}, and all getter
* methods that return a non-{@code null} value and have a
* {@link LDAPGetter} annotation with the {@code inFilter} element set to
* {@code true}.
*
* @param o The object for which to create the search filter.
*
* @return A filter that can be used to search for entries matching the
* provided object.
*
* @throws LDAPPersistException If it is not possible to construct a search
* filter for some reason (e.g., because the
* provided object does not have any
* non-{@code null} fields or getters that are
* marked for inclusion in filters).
*/
@NotNull()
public Filter createFilter(@NotNull final T o)
throws LDAPPersistException
{
final AtomicBoolean addedRequiredOrAllowed = new AtomicBoolean(false);
final Filter f = createFilter(o, addedRequiredOrAllowed);
if (! addedRequiredOrAllowed.get())
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_OR_ALLOWED.get());
}
return f;
}
/**
* Retrieves a filter that can be used to search for entries matching the
* provided object. It will be constructed as an AND search using all fields
* with a non-{@code null} value and that have a {@link LDAPField} annotation
* with the {@code inFilter} element set to {@code true}, and all getter
* methods that return a non-{@code null} value and have a
* {@link LDAPGetter} annotation with the {@code inFilter} element set to
* {@code true}.
*
* @param o The object for which to create the search
* filter.
* @param addedRequiredOrAllowed Indicates whether any filter elements from
* required or allowed fields or getters have
* been added to the filter yet.
*
* @return A filter that can be used to search for entries matching the
* provided object.
*
* @throws LDAPPersistException If it is not possible to construct a search
* filter for some reason (e.g., because the
* provided object does not have any
* non-{@code null} fields or getters that are
* marked for inclusion in filters).
*/
@NotNull()
private Filter createFilter(@NotNull final T o,
@NotNull final AtomicBoolean addedRequiredOrAllowed)
throws LDAPPersistException
{
final ArrayList attrs = new ArrayList<>(5);
attrs.add(objectClassAttribute);
for (final FieldInfo i : requiredFilterFields)
{
final Attribute a = i.encode(o, true);
if (a == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_FIELD.get(
i.getField().getName()));
}
else
{
attrs.add(a);
addedRequiredOrAllowed.set(true);
}
}
for (final GetterInfo i : requiredFilterGetters)
{
final Attribute a = i.encode(o);
if (a == null)
{
throw new LDAPPersistException(
ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_GETTER.get(
i.getMethod().getName()));
}
else
{
attrs.add(a);
addedRequiredOrAllowed.set(true);
}
}
for (final FieldInfo i : alwaysAllowedFilterFields)
{
final Attribute a = i.encode(o, true);
if (a != null)
{
attrs.add(a);
addedRequiredOrAllowed.set(true);
}
}
for (final GetterInfo i : alwaysAllowedFilterGetters)
{
final Attribute a = i.encode(o);
if (a != null)
{
attrs.add(a);
addedRequiredOrAllowed.set(true);
}
}
for (final FieldInfo i : conditionallyAllowedFilterFields)
{
final Attribute a = i.encode(o, true);
if (a != null)
{
attrs.add(a);
}
}
for (final GetterInfo i : conditionallyAllowedFilterGetters)
{
final Attribute a = i.encode(o);
if (a != null)
{
attrs.add(a);
}
}
final ArrayList comps = new ArrayList<>(attrs.size());
for (final Attribute a : attrs)
{
for (final ASN1OctetString v : a.getRawValues())
{
comps.add(Filter.createEqualityFilter(a.getName(), v.getValue()));
}
}
if (superclassHandler != null)
{
final Filter f =
superclassHandler.createFilter(o, addedRequiredOrAllowed);
if (f.getFilterType() == Filter.FILTER_TYPE_AND)
{
comps.addAll(Arrays.asList(f.getComponents()));
}
else
{
comps.add(f);
}
}
return Filter.createANDFilter(comps);
}
}