package org.ilrt.mca.services.ldap;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.DC;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.jena.vocabulary.VCARD;
import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;
import org.ilrt.mca.services.SearchService;
import org.ilrt.mca.vocab.FOAF;
import org.ilrt.mca.vocab.MCA_REGISTRY;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Hashtable;
public class BasicLdapSearch implements SearchService {
/**
* Constructor for the ldap search service. Two hash tables need to be provided. First,
* the connection details for the ldap server. These are related to javax.naming.Context
* and contain values for "java.naming.provider.url", "java.naming.security.protocol" etc.
* Second, more general configuration details for retrieving data, such as mapping
* attributes and defining a search template.
*
* @param env the values need to connect to an LDAP server.
* @param cfg the values that provide mapping for attributes.
*/
public BasicLdapSearch(Hashtable<Object, Object> env, Hashtable<Object, Object> cfg) {
this.env = env;
// get the limit of results
try {
RESULT_LIMIT = Integer.valueOf((String) cfg.get("resultLimit"));
} catch (NumberFormatException ex) {
RESULT_LIMIT = 10;
}
// get the attribute mapping values
uidMapping = (String) cfg.get("uid");
displayNameMapping = (String) cfg.get("displayName");
titleMapping = (String) cfg.get("title");
ouMapping = (String) cfg.get("ou");
postalAddressMapping = (String) cfg.get("postalAddress");
postCodeMapping = (String) cfg.get("postCode");
mailMapping = (String) cfg.get("mail");
telephoneNumberMapping = (String) cfg.get("telephoneNumber");
// person URI prefix
personUriPrefix = (String) cfg.get("personUriPrefix");
// base DN for searches
baseDN = (String) cfg.get("baseDN");
}
@Override
public Resource search(Object... args) {
if (args.length != 2) {
throw new RuntimeException("Received " + args.length + " arguments. Expected 2");
}
Resource r = (Resource) args[0];
String filter = (String) args[1];
try {
log.info("Connecting to LDAP server.");
DirContext ctx = new InitialDirContext(env);
SearchControls ctls = new SearchControls();
ctls.setCountLimit(RESULT_LIMIT);
NamingEnumeration<SearchResult> results =
ctx.search(baseDN, filter, ctls);
while (results.hasMore()) {
createContact(r, results.nextElement());
}
} catch (SizeLimitExceededException ex) {
r.addProperty(DC.description, "Your search returned more than " + RESULT_LIMIT +
" results. To refine your search, try entering more details (e.g. " +
"forename and surname).");
log.info("Search results are limited");
} catch (NamingException ex) {
ex.printStackTrace();
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
return r;
}
private void createContact(Resource r, SearchResult result) throws NamingException,
NoSuchAlgorithmException {
// get the attributes from the directory
Attributes attributes = result.getAttributes();
// get the unique identifier for the user
String uid = (String) attributes.get(uidMapping).get();
String personUri = generateUri(uid);
// TODO - URI for the person?
Resource resource = r.getModel().createResource(personUri);
// name
if (displayNameMapping != null) {
if (attributes.get(displayNameMapping) != null) {
String name = (String) attributes.get(displayNameMapping).get();
resource.addLiteral(VCARD.NAME, name);
}
}
// job title
if (titleMapping != null) {
if (attributes.get(titleMapping) != null) {
String title = (String) attributes.get(titleMapping).get();
resource.addLiteral(VCARD.TITLE, title);
}
}
// organizational unit
if (ouMapping != null) {
if (attributes.get(ouMapping) != null) {
String ou = (String) attributes.get(ouMapping).get();
resource.addLiteral(VCARD.Orgname, ou);
}
}
// address
if (postalAddressMapping != null) {
if (attributes.get(postalAddressMapping) != null) {
String address = (String) attributes.get(postalAddressMapping).get();
if (postCodeMapping != null) { // post code might be part of the address
if (attributes.get(postCodeMapping) != null) {
address = address + "," + attributes.get(postCodeMapping).get();
}
}
resource.addLiteral(VCARD.ADR, address);
}
}
// email
if (mailMapping != null) {
if (attributes.get(mailMapping) != null) {
String mail = (String) attributes.get(mailMapping).get();
resource.addLiteral(FOAF.mbox, mail);
}
}
// telephone number
if (telephoneNumberMapping != null) {
if (attributes.get(telephoneNumberMapping) != null) {
String tel = (String) attributes.get(telephoneNumberMapping).get();
resource.addProperty(FOAF.phone, handleTelephone(r.getModel(), tel));
}
}
r.addProperty(MCA_REGISTRY.hasItem, resource);
}
private Resource handleTelephone(Model m, String telNumber) {
// create format that can be used by phones
String telNumberUri = "tel:" + telNumber.replace(" ", "");
telNumberUri = telNumberUri.replace("(0)", "");
Resource r = m.createResource(telNumberUri);
r.addProperty(RDFS.label, telNumber);
return r;
}
/**
* In Bristol, policy dictates we can't advertise the UID externally.
* I'd like a unique identifier, so hash the UID. Performance issues?
*
* @param uid the unique identifier of a person in the directory.
* @return a URI to represented the person based in a hash of the URI.
*/
private String generateUri(String uid) {
try {
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(uid.getBytes());
byte hash[] = digest.digest();
char[] hex = Hex.encodeHex(hash); // create a hex value of the hash
return personUriPrefix + new String(hex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
Logger log = Logger.getLogger(org.ilrt.mca.services.ldap.BasicLdapSearch.class);
private final Hashtable env;
private int RESULT_LIMIT;
private String baseDN;
private String personUriPrefix;
private String uidMapping;
private String displayNameMapping;
private String titleMapping;
private String ouMapping;
private String postalAddressMapping;
private String postCodeMapping;
private String mailMapping;
private String telephoneNumberMapping;
}