/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.io;
import java.io.IOException;
import java.io.Reader;
import java.util.HashSet;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.LdapUtils;
import org.ldaptive.SearchResult;
import org.ldaptive.SortBehavior;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Reads DSML version 1 from a {@link Reader} and returns a {@link SearchResult}.
*
* @author Middleware Services
*/
public class Dsmlv1Reader implements SearchResultReader
{
/** Document builder factory. */
private static final DocumentBuilderFactory DOC_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
/**
* Initialize the document builder factory.
*/
static {
DOC_BUILDER_FACTORY.setNamespaceAware(true);
}
/** Reader to read from. */
private final Reader dsmlReader;
/** Sort behavior. */
private final SortBehavior sortBehavior;
/**
* Creates a new dsml reader.
*
* @param reader to read DSML from
*/
public Dsmlv1Reader(final Reader reader)
{
this(reader, SortBehavior.getDefaultSortBehavior());
}
/**
* Creates a new dsml reader.
*
* @param reader to read DSML from
* @param sb sort behavior of the ldap result
*/
public Dsmlv1Reader(final Reader reader, final SortBehavior sb)
{
dsmlReader = reader;
if (sb == null) {
throw new IllegalArgumentException("Sort behavior cannot be null");
}
sortBehavior = sb;
}
/**
* Reads DSML data from the reader and returns a search result.
*
* @return search result derived from the DSML
*
* @throws IOException if an error occurs using the reader
*/
@Override
public SearchResult read()
throws IOException
{
try {
final DocumentBuilder db = DOC_BUILDER_FACTORY.newDocumentBuilder();
final Document dsml = db.parse(new InputSource(dsmlReader));
return createSearchResult(dsml);
} catch (ParserConfigurationException | SAXException e) {
throw new IOException(e);
}
}
/**
* Creates a search result that corresponds to the supplied DSML document.
*
* @param doc DSML to parse
*
* @return search result
*/
protected SearchResult createSearchResult(final Document doc)
{
final SearchResult result = new SearchResult(sortBehavior);
if (doc != null && doc.hasChildNodes()) {
final NodeList nodes = doc.getElementsByTagName("dsml:entry");
for (int i = 0; i < nodes.getLength(); i++) {
final LdapEntry le = createLdapEntry((Element) nodes.item(i));
result.addEntry(le);
}
}
return result;
}
/**
* Converts the supplied DSML entry element into an ldap entry object.
*
* @param entryElement to parse
*
* @return ldap entry
*/
protected LdapEntry createLdapEntry(final Element entryElement)
{
final LdapEntry ldapEntry = new LdapEntry(sortBehavior);
ldapEntry.setDn("");
if (entryElement != null) {
final String name = entryElement.getAttribute("dn");
if (name != null) {
ldapEntry.setDn(name);
}
if (entryElement.hasChildNodes()) {
final NodeList ocNodes = entryElement.getElementsByTagName("dsml:objectclass");
if (ocNodes.getLength() > 0) {
final Element ocElement = (Element) ocNodes.item(0);
if (ocElement != null && ocElement.hasChildNodes()) {
final LdapAttribute ldapAttribute = createLdapAttribute(
"objectClass",
ocElement.getElementsByTagName("dsml:oc-value"));
ldapEntry.addAttribute(ldapAttribute);
}
}
final NodeList attrNodes = entryElement.getElementsByTagName("dsml:attr");
for (int i = 0; i < attrNodes.getLength(); i++) {
final Element attrElement = (Element) attrNodes.item(i);
final String attrName = attrElement.hasAttribute("name") ? attrElement.getAttribute("name") : null;
if (attrName != null && attrElement.hasChildNodes()) {
final LdapAttribute ldapAttribute = createLdapAttribute(
attrName,
attrElement.getElementsByTagName("dsml:value"));
ldapEntry.addAttribute(ldapAttribute);
}
}
}
}
return ldapEntry;
}
/**
* Returns an ldap attribute derived from the supplied node list.
*
* @param name of the ldap attribute
* @param nodes to parse
*
* @return ldap attribute
*/
protected LdapAttribute createLdapAttribute(final String name, final NodeList nodes)
{
boolean isBase64 = false;
final Set<Object> values = new HashSet<>();
for (int i = 0; i < nodes.getLength(); i++) {
final Element valueElement = (Element) nodes.item(i);
if (valueElement != null) {
if (valueElement.hasAttribute("encoding") && "base64".equals(valueElement.getAttribute("encoding"))) {
isBase64 = true;
}
values.add(getAttrValue(valueElement, isBase64));
}
}
return LdapAttribute.createLdapAttribute(sortBehavior, name, values);
}
/**
* Returns the value of the supplied element taking into account whether the value needs to be base64 decoded.
*
* @param valueElement to read value from
* @param base64 whether to base64 decode the value
*
* @return String or byte[] depending on the base64 flag
*/
protected Object getAttrValue(final Element valueElement, final boolean base64)
{
final String value = valueElement.getChildNodes().item(0).getNodeValue();
if (base64) {
return LdapUtils.base64Decode(value);
}
return value;
}
}