All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ldaptive.beans.generate.BeanGenerator Maven / Gradle / Ivy

Go to download

Provides a mapping, persistence, and code generation API for reading and writing POJOs to an LDAP directory

There is a newer version: 2.4.0
Show newest version
/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.beans.generate;

import java.io.File;
import java.io.IOException;
import java.security.cert.Certificate;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
import com.sun.codemodel.JAnnotationArrayMember;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocComment;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JVar;
import org.ldaptive.beans.generate.props.BeanGeneratorPropertySource;
import org.ldaptive.schema.AttributeType;
import org.ldaptive.schema.AttributeUsage;
import org.ldaptive.schema.ObjectClass;
import org.ldaptive.schema.Schema;
import org.ldaptive.schema.Syntax;

/**
 * Utility class for creating Java POJOs from an LDAP schema. Sample usage:
 *
 * 
     Schema schema = new Schema(new DefaultConnectionFactory(
       "ldap://directory.ldaptive.org"));
     BeanGenerator generator = new BeanGenerator(
       schema,
       "com.my.package",
       new String[] {"inetOrgPerson"});
     generator.generate();
     generator.write();
 * 
* * @author Middleware Services */ public class BeanGenerator { /** Code model for java class creation. */ private final JCodeModel codeModel = new JCodeModel(); /** Schema to generate beans from. */ private Schema schema; /** Package to create beans in. */ private String packageName; /** Object classes to build beans for. */ private String[] objectClasses; /** Whether to include optional attributes. */ private boolean useOptionalAttributes = true; /** Whether to include operational attributes. */ private boolean useOperationalAttributes; /** Whether to include superior classes for each object class. */ private boolean includeSuperiorClasses; /** Mapping to determine attribute value type. */ private Map> typeMappings = getDefaultTypeMappings(); /** Name mappings. */ private Map nameMappings = new HashMap<>(); /** Excluded names. */ private String[] excludedNames = new String[0]; /** Default constructor. */ public BeanGenerator() {} /** * Creates a new bean generator. A bean will be generated for each supplied object class. * * @param s schema containing directory data for generation * @param name package name to place the generated classes in * @param oc object classes to generate beans for */ public BeanGenerator(final Schema s, final String name, final String[] oc) { schema = s; packageName = name; objectClasses = oc; } /** * Returns the schema. * * @return schema */ public Schema getSchema() { return schema; } /** * Sets the schema. * * @param s schema */ public void setSchema(final Schema s) { schema = s; } /** * Returns the package name where beans will be generated. * * @return package name */ public String getPackageName() { return packageName; } /** * Sets the package name where beans will be generated. * * @param name package name */ public void setPackageName(final String name) { packageName = name; } /** * Returns the object classes. A class is generated for each object class. * * @return object classes */ public String[] getObjectClasses() { return objectClasses; } /** * Sets the object classes. A class is generated for each object class. * * @param oc object classes */ public void setObjectClasses(final String... oc) { objectClasses = oc; } /** * Returns whether to include optional attributes in bean generation. * * @return whether to include optional attributes */ public boolean isUseOptionalAttributes() { return useOptionalAttributes; } /** * Sets whether to include optional attributes in bean generation. * * @param b whether to include optional attributes */ public void setUseOptionalAttributes(final boolean b) { useOptionalAttributes = b; } /** * Returns whether to include operational attributes in bean generation. * * @return whether to include operational attributes */ public boolean isUseOperationalAttributes() { return useOperationalAttributes; } /** * Sets whether to include operational attributes in bean generation. * * @param b whether to include operational attributes */ public void setUseOperationalAttributes(final boolean b) { useOperationalAttributes = b; } /** * Returns whether to include superior classes in bean generation. * * @return whether to include superior classes attributes */ public boolean isIncludeSuperiorClasses() { return includeSuperiorClasses; } /** * Sets whether to include superior classes in bean generation. * * @param b whether to include superior classes */ public void setIncludeSuperiorClasses(final boolean b) { includeSuperiorClasses = b; } /** * Returns the type mappings. Type mappings is syntax OID to class type and is used to determine field type in the * generated POJOs. * * @return type mappings */ public Map> getTypeMappings() { return typeMappings; } /** * Sets the type mappings. Type mappings is syntax OID to class type and is used to determine field type in the * generated POJOs. * * @param m type mappings */ public void setTypeMappings(final Map> m) { typeMappings = m; } /** * Returns the mapping of directory attribute name to bean property. This property is used to override the default * schema name. For instance, you may prefer using 'countryName' to 'c', which would be set as 'c'=>'countryName'. * * @return attribute name to bean property mapping */ public Map getNameMappings() { return nameMappings; } /** * Sets the mapping of directory attribute name to bean property. * * @param m name mappings * * @throws NullPointerException if m is null */ public void setNameMappings(final Map m) { if (m == null) { throw new NullPointerException("Name mappings cannot be null"); } nameMappings = m; } /** * Returns the attribute names to exclude from bean generation. Excludes an attribute from the generated POJO. For * instance, you may not want 'userPassword' included in your bean. * * @return attribute names to exclude */ public String[] getExcludedNames() { return excludedNames; } /** * Sets the attribute names to exclude from bean generation. * * @param names to exclude * * @throws NullPointerException if names is null */ public void setExcludedNames(final String... names) { if (names == null) { throw new NullPointerException("Excluded names cannot be null"); } excludedNames = names; } /** * Returns the default syntax types used to determine attribute property type. * * @return map of syntax OID to class type */ protected static Map> getDefaultTypeMappings() { final Map> m = new HashMap<>(); m.put("1.3.6.1.4.1.1466.115.121.1.7", Boolean.class); m.put("1.3.6.1.4.1.1466.115.121.1.5", byte[].class); m.put("1.3.6.1.4.1.1466.115.121.1.8", Certificate.class); m.put("1.3.6.1.4.1.1466.115.121.1.24", ZonedDateTime.class); m.put("1.3.6.1.4.1.1466.115.121.1.36", Integer.class); m.put("1.3.6.1.1.16.1", UUID.class); return m; } /** * Returns the class for the supplied attribute type and syntax. If the attribute type syntax OID is found in the * default type mapping it is used. Otherwise if the syntax is "X-NOT-HUMAN-READABLE", a byte array is used. * * @param type attribute type * @param syntax associated with the attribute type * * @return syntax type */ protected Class getSyntaxType(final AttributeType type, final Syntax syntax) { Class t = null; for (Map.Entry> entry : typeMappings.entrySet()) { if (entry.getKey().equals(type.getSyntaxOID(false))) { t = entry.getValue(); } } if (t == null) { if (Syntax.containsBooleanExtension(syntax, "X-NOT-HUMAN-READABLE")) { t = byte[].class; } else { t = String.class; } } return t; } /** * Generates a class for each configured object class. See {@link #objectClasses}. {@link #write(String)} must be * invoked to write the classes to disk. */ public void generate() { for (String objectClass : objectClasses) { final JDefinedClass definedClass = createClass(packageName, objectClass); final JDocComment jDocComment = definedClass.javadoc(); jDocComment.add(String.format("Ldaptive generated bean for objectClass '%s'", objectClass)); final ObjectClass oc = schema.getObjectClass(objectClass); final Set attributeNames = getAttributeNames(oc); if (useOperationalAttributes) { attributeNames.addAll( schema.getAttributeTypes().stream().filter( type -> AttributeUsage.DIRECTORY_OPERATION.equals(type.getUsage())).map( AttributeType::getName).collect(Collectors.toList())); } final Map mutators = new TreeMap<>(); for (String name : attributeNames) { final AttributeType type = schema.getAttributeType(name); if (!isNameExcluded(type)) { if (nameMappings.containsKey(type.getName())) { mutators.put(nameMappings.get(type.getName()), type); } else { mutators.put(formatAttributeName(type.getName()), type); } } } // add entry annotation final JAnnotationUse entryAnnotation = definedClass.annotate(codeModel.ref(org.ldaptive.beans.Entry.class)); entryAnnotation.param("dn", "dn"); final JAnnotationArrayMember attrArray = entryAnnotation.paramArray("attributes"); // add mutator for the DN createMutators(definedClass, "dn", String.class, false); // add mutators for each attribute for (Map.Entry mutator : mutators.entrySet()) { final Class syntaxType = getSyntaxType( mutator.getValue(), schema.getSyntax(mutator.getValue().getSyntaxOID(false))); createMutators(definedClass, mutator.getKey(), syntaxType, !mutator.getValue().isSingleValued()); // add attribute annotation final JAnnotationUse attrAnnotation = attrArray.annotate(org.ldaptive.beans.Attribute.class); attrAnnotation.param("name", mutator.getValue().getName()); if (!mutator.getKey().equals(mutator.getValue().getName())) { attrAnnotation.param("property", mutator.getKey()); } if (byte[].class.equals(syntaxType)) { attrAnnotation.param("binary", true); } } // create additional methods createHashCode(definedClass); createEquals(definedClass); createToString(definedClass); } } /** * Returns the attribute names to use for the supplied object class. See {@link #getAttributeNames(ObjectClass, Set)}. * * @param objectClass to retrieve names from * * @return set of all attribute names used for bean generation */ private Set getAttributeNames(final ObjectClass objectClass) { return getAttributeNames(objectClass, new HashSet<>()); } /** * Returns the attribute names to use for the supplied object class. This method is invoked recursively if superior * classes are included. * * @param objectClass to retrieve names from * @param processed object classes that have already been processed * * @return set of all attribute names used for bean generation */ private Set getAttributeNames(final ObjectClass objectClass, final Set processed) { final Set attributeNames = new HashSet<>(); if (objectClass != null) { if (objectClass.getRequiredAttributes() != null) { attributeNames.addAll(Arrays.asList(objectClass.getRequiredAttributes())); } if (useOptionalAttributes && objectClass.getOptionalAttributes() != null) { attributeNames.addAll(Arrays.asList(objectClass.getOptionalAttributes())); } processed.add(objectClass); if (includeSuperiorClasses && objectClass.getSuperiorClasses() != null) { for (String oc : objectClass.getSuperiorClasses()) { final ObjectClass superiorOc = schema.getObjectClass(oc); if (!processed.contains(superiorOc)) { attributeNames.addAll(getAttributeNames(superiorOc, processed)); } } } } return attributeNames; } /** * Formats the supplied name for use as a Java property. * * @param name to format * * @return formatted name */ private String formatAttributeName(final String name) { final String formatted; if (name.contains("-")) { formatted = name.replace("-", ""); } else { formatted = name; } return formatted; } /** * Returns whether the supplied attribute type has a matching OID or name in the excluded names list. * * @param type to compare * * @return whether attribute type should be excluded from bean generation */ private boolean isNameExcluded(final AttributeType type) { if (excludedNames != null && excludedNames.length > 0) { for (String excluded : excludedNames) { if (type.getOID().equals(excluded) || type.hasName(excluded)) { return true; } } } return false; } /** * Creates a class in the supplied package. * * @param classPackage to place the class in * @param className to create * * @return class * * @throws IllegalArgumentException if the class already exists */ protected JDefinedClass createClass(final String classPackage, final String className) { final String fqClassName; if (!Character.isUpperCase(className.charAt(0))) { fqClassName = String.format( "%s.%s", classPackage, className.substring(0, 1).toUpperCase() + className.substring(1)); } else { fqClassName = String.format("%s.%s", classPackage, className); } try { return codeModel._class(fqClassName); } catch (JClassAlreadyExistsException e) { throw new IllegalArgumentException("Class already exists: " + fqClassName, e); } } /** * Creates the getter and setter methods on the supplied class for the supplied name. * * @param clazz to put getter and setter methods on * @param name of the property * @param syntaxType of the property * @param multivalue whether this property is a collection */ protected void createMutators( final JDefinedClass clazz, final String name, final Class syntaxType, final boolean multivalue) { final String upperName = name.substring(0, 1).toUpperCase() + name.substring(1); if (multivalue) { final JClass detailClass = codeModel.ref(syntaxType); final JClass collectionClass = codeModel.ref(Collection.class); final JClass genericClass = collectionClass.narrow(detailClass); final JFieldVar field = clazz.field(JMod.PRIVATE, genericClass, name); final JMethod getterMethod = clazz.method(JMod.PUBLIC, genericClass, "get" + upperName); getterMethod.body()._return(field); final JMethod setterMethod = clazz.method(JMod.PUBLIC, Void.TYPE, "set" + upperName); setterMethod.param(genericClass, "c"); setterMethod.body().assign(JExpr._this().ref(name), JExpr.ref("c")); } else { final JFieldVar field = clazz.field(JMod.PRIVATE, syntaxType, name); final JMethod getterMethod = clazz.method(JMod.PUBLIC, syntaxType, "get" + upperName); getterMethod.body()._return(field); final JMethod setterMethod = clazz.method(JMod.PUBLIC, Void.TYPE, "set" + upperName); setterMethod.param(syntaxType, "s"); setterMethod.body().assign(JExpr._this().ref(name), JExpr.ref("s")); } } /** * Creates the hashCode method on the supplied class. Leverages {@link org.ldaptive.LdapUtils#computeHashCode(int, * Object...)}. * * @param clazz to put hashCode method on */ private void createHashCode(final JDefinedClass clazz) { final JClass ldapUtilsClass = codeModel.ref(org.ldaptive.LdapUtils.class); final JInvocation computeHashCode = ldapUtilsClass.staticInvoke("computeHashCode"); final JMethod hashCode = clazz.method(JMod.PUBLIC, int.class, "hashCode"); hashCode.annotate(java.lang.Override.class); // CheckStyle:MagicNumber OFF computeHashCode.arg(JExpr.lit(7919)); // CheckStyle:MagicNumber ON for (Map.Entry entry : clazz.fields().entrySet()) { computeHashCode.arg(JExpr._this().ref(entry.getValue())); } hashCode.body()._return(computeHashCode); } /** * Creates the equals method on the supplied class. Leverages {@link org.ldaptive.LdapUtils#areEqual(Object, Object)}. * * @param clazz to put equals method on */ private void createEquals(final JDefinedClass clazz) { final JMethod equals = clazz.method(JMod.PUBLIC, boolean.class, "equals"); equals.annotate(java.lang.Override.class); final JVar o = equals.param(Object.class, "o"); final JConditional ifSame = equals.body()._if(o.eq(JExpr._this())); ifSame._then()._return(JExpr.TRUE); final JConditional ifInstance = equals.body()._if(o._instanceof(clazz)); final JVar v = ifInstance._then().decl(clazz, "v", JExpr.cast(clazz, o)); JExpression propertyComparison = null; for (Map.Entry entry : clazz.fields().entrySet()) { final JClass ldapUtilsClass = codeModel.ref(org.ldaptive.LdapUtils.class); final JInvocation areEqual = ldapUtilsClass.staticInvoke("areEqual"); areEqual.arg(entry.getValue()); areEqual.arg(v.ref(entry.getValue())); if (propertyComparison == null) { propertyComparison = areEqual; } else { propertyComparison = propertyComparison.cand(areEqual); } } ifInstance._then()._return(propertyComparison); equals.body()._return(JExpr.FALSE); } /** * Creates the toString method on the supplied class. Creates a string that contains every property on the generated * bean. * * @param clazz to put toString method on */ private void createToString(final JDefinedClass clazz) { final JClass stringClass = codeModel.ref(java.lang.String.class); final JInvocation format = stringClass.staticInvoke("format"); final JMethod toString = clazz.method(JMod.PUBLIC, String.class, "toString"); toString.annotate(java.lang.Override.class); final StringBuilder sb = new StringBuilder("[%s@%d::"); for (Map.Entry entry : clazz.fields().entrySet()) { sb.append(entry.getKey()).append("=%s, "); } sb.setLength(sb.length() - 2); sb.append("]"); format.arg(sb.toString()); format.arg(JExpr._this().invoke("getClass").invoke("getName")); format.arg(JExpr._this().invoke("hashCode")); for (Map.Entry entry : clazz.fields().entrySet()) { format.arg(JExpr._this().ref(entry.getValue())); } toString.body()._return(format); } /** * Writes the generated classes to disk. Invokes {@link #write(String)} with ".". * * @throws IOException if the write fails */ public void write() throws IOException { write("."); } /** * Writes the generated classes to disk at the supplied path. * * @param path to write the classes to * * @throws IOException if the write fails */ public void write(final String path) throws IOException { final File f = new File(path); if (!f.exists()) { f.mkdirs(); } codeModel.build(f); } /** * Provides command line access to a {@link BeanGenerator}. Expects two arguments: * *
    *
  1. path to a configuration property file
  2. *
  3. target directory to write files to
  4. *
* *

A sample configuration property file looks like:

* *
     org.ldaptive.packageName=my.package.ldap.beans
     org.ldaptive.objectClasses=eduPerson
     org.ldaptive.nameMappings=c=countryName,l=localityName
     org.ldaptive.excludedNames=userPassword
     org.ldaptive.ldapUrl=ldap://directory.ldaptive.org
   * 
* * @param args command line arguments * * @throws Exception if any error occurs */ public static void main(final String[] args) throws Exception { final String propsPath = args[0]; final String targetPath = args[1]; final BeanGenerator generator = new BeanGenerator(); final BeanGeneratorPropertySource source = new BeanGeneratorPropertySource(generator, propsPath); source.initialize(); generator.generate(); generator.write(targetPath); } /** * Creates a builder for this class. * * @return new builder */ public static Builder builder() { return new Builder(); } // CheckStyle:OFF public static class Builder { private final BeanGenerator object = new BeanGenerator(); protected Builder() {} public Builder schema(final Schema schema) { object.setSchema(schema); return this; } public Builder packageName(final String name) { object.setPackageName(name); return this; } public Builder objectClasses(final String... classes) { object.setObjectClasses(classes); return this; } public Builder useOptionalAttributes(final boolean b) { object.setUseOptionalAttributes(b); return this; } public Builder useOperationalAttributes(final boolean b) { object.setUseOperationalAttributes(b); return this; } public Builder includeSuperiorClasses(final boolean b) { object.setIncludeSuperiorClasses(b); return this; } public Builder typeMappings(final Map> mappings) { object.setTypeMappings(mappings); return this; } public Builder nameMappings(final Map mappings) { object.setNameMappings(mappings); return this; } public Builder excludedNames(final String... classes) { object.setExcludedNames(classes); return this; } public BeanGenerator build() { return object; } } // CheckStyle:ON }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy