package org.argeo.naming; import static org.argeo.naming.LdapAttrs.DN; import static org.argeo.naming.LdapAttrs.member; import static org.argeo.naming.LdapAttrs.objectClass; import static org.argeo.naming.LdapAttrs.uniqueMember; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import org.argeo.osgi.useradmin.UserDirectoryException; /** Basic LDIF writer */ public class LdifWriter { private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private final Writer writer; /** Writer must be closed by caller */ public LdifWriter(Writer writer) { this.writer = writer; } /** Stream must be closed by caller */ public LdifWriter(OutputStream out) { this(new OutputStreamWriter(out, DEFAULT_CHARSET)); } public void writeEntry(LdapName name, Attributes attributes) throws IOException { try { // check consistency Rdn nameRdn = name.getRdn(name.size() - 1); Attribute nameAttr = attributes.get(nameRdn.getType()); if (!nameAttr.get().equals(nameRdn.getValue())) throw new UserDirectoryException( "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name); writer.append(DN + ": ").append(name.toString()).append('\n'); Attribute objectClassAttr = attributes.get(objectClass.name()); if (objectClassAttr != null) writeAttribute(objectClassAttr); attributes: for (NamingEnumeration attrs = attributes.getAll(); attrs.hasMore();) { Attribute attribute = attrs.next(); if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name())) continue attributes;// skip DN attribute if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name())) continue attributes;// skip member and uniqueMember attributes, so that they are always written last writeAttribute(attribute); } // write member and uniqueMember attributes last for (NamingEnumeration attrs = attributes.getAll(); attrs.hasMore();) { Attribute attribute = attrs.next(); if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name())) writeMemberAttribute(attribute); } writer.append('\n'); writer.flush(); } catch (NamingException e) { throw new UserDirectoryException("Cannot write LDIF", e); } } public void write(Map entries) throws IOException { for (LdapName dn : entries.keySet()) writeEntry(dn, entries.get(dn)); } protected void writeAttribute(Attribute attribute) throws NamingException, IOException { for (NamingEnumeration attrValues = attribute.getAll(); attrValues.hasMore();) { Object value = attrValues.next(); if (value instanceof byte[]) { String encoded = Base64.getEncoder().encodeToString((byte[]) value); writer.append(attribute.getID()).append(":: ").append(encoded).append('\n'); } else { writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n'); } } } protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException { // Note: duplicate entries will be swallowed SortedSet values = new TreeSet<>(); for (NamingEnumeration attrValues = attribute.getAll(); attrValues.hasMore();) { String value = attrValues.next().toString(); values.add(value); } for (String value : values) { writer.append(attribute.getID()).append(": ").append(value).append('\n'); } } }