LdapRemapper.java
/*
** Module :LdapRemapper.java
** Abstract :LDAP back-end for the Directory.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SIY 20050322 @20581 Created initial version
** 002 SIY 20050406 @21016 Changes required to support writing,
** different mapping modes and map sources,
** etc.
** 003 SIY 20050513 @21196 Commented out printing stack trace in
** deleteLdapNodeAttribute, cleaned up comments.
** 004 SIY 20050518 @21234 Reestablish connection on bind() if it is
** failed.
** 005 SIY 20050524 @21263 Fixed moving nodes.
** 006 SIY 20090514 @42183 Added refresh() method, some minor cleanups.
** 007 EVL 20160222 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 008 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 009 SP 20250417 Code formatting and javadoc fixes.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.directory;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.cfg.ConfigurationException;
import com.goldencode.p2j.schema.SchemaException;
import com.goldencode.p2j.util.logging.*;
/**
* This class provides a mapping of the P2J Directory tree into LDAP
* directory.
* <p>
* The design assumes that LdapRemapper can be used in very different
* environments and therefore it must support a number of different approaches
* which allow mapping between P2J directory as it is seen by the
* application and underlying LDAP directory.
* <p>
* Following cases are possible:
* <ul>
* <li>Direct mapping. <br>
* In this case each P2J node has appropriate underling LDAP node.</li>
* <li>Partial mapping. <br>
* In this case some P2J nodes may have no appropriate LDAP nodes. In extreme
* case only leaf nodes may have appropriate LDAP nodes while all other nodes
* are only virtual.</li>
* <li>Partial mapping with some data stored outside LDAP. <br>
* This case is similar to previous one but even not all leaf nodes are really
* backed by the LDAP nodes and stored elsewhere.</li>
* </ul>
* <p>
* To support all these different situations following approach is used:
* implementation maintains P2J directory tree in memory. Each node which is
* backed by the LDAP has special Object Class "ldapNode". This class
* represent all information required to properly access LDAP server and map
* attributes. The mapping is constructed at startup either by loading data
* from external storage or by scanning LDAP subtree.
*
* @author SIY
* @version 1.0
*/
class LdapRemapper
extends RamRemapper
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(LdapRemapper.class);
/** Value which will represent empty strings in LDAP */
private static final String EMPTY = "--empty--";
/** Mode: mapping saved as attribute in LDAP node */
private static final int MODE_ATTRIBUTE = 1;
/** Mode: mapping is XML file */
private static final int MODE_FILE = 2;
/** Mode: mapping saved as subtree of LDAP tree */
private static final int MODE_SUBTREE = 3;
/** Mode: mapping is XML file at specified URL */
private static final int MODE_URL = 4;
/** Certificate alias */
private String alias;
/** Certificate password */
private String aliasPass;
/** LDAP directory context used to access LDAP data */
private LdapContext ctx;
/** LDAP context environment */
private Hashtable<String, String> env;
/** Keystore password */
private String keyPass;
/** Keystore */
private String keyStore;
/** Mapping mapping (file, URL, CN, etc) */
private String mapping;
/** How to handle mapping field.*/
private int mode;
/** Mapping data */
private SchemaMapping schemaMap;
/** Truststore password */
private String trustPass;
/** Storage for the private keys */
private String trustStore;
/**
* Construct an instance of the LdapRemapper which will work with specified
* LDAP server.
*
* @param config
* A reference to configuration.
*
* @throws ConfigurationException
*/
LdapRemapper(BootstrapConfig config) throws ConfigurationException
{
super();
schemaMap = new SchemaMapping();
mapping = config.getConfigItem("directory", "ldap", "MAPPING");
String modeStr = config.getConfigItem("directory", "ldap", "MODE");
if (modeStr == null)
{
throw new ConfigurationException("Mapping mode must be defined");
}
else if (modeStr.equalsIgnoreCase("FILE")) //Local XML file
{
//mapping is a file name
mode = MODE_FILE;
}
else if (modeStr.equalsIgnoreCase("URL")) //External URL to XML data
{
//mapping is an URL
mode = MODE_URL;
}
else if (modeStr.equalsIgnoreCase("ATTRIBUTE")) //LDAP attribute
{
//mapping is full path to the attribute
mode = MODE_ATTRIBUTE;
}
else if (modeStr.equalsIgnoreCase("SUBTREE")) //Direct mapping
{
//mapping is a root of the mapping subtree
mode = MODE_SUBTREE;
}
else
{
throw new ConfigurationException("Unknown mapping mode");
}
if (mapping == null)
{
throw new ConfigurationException("Mapping must be defined");
}
env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
String url = config.getConfigItem("directory", "ldap", "URL");
if (url != null)
{
env.put(Context.PROVIDER_URL, url);
}
else
{
throw new ConfigurationException("Missing LDAP URL");
}
String param = config.getConfigItem("directory", "ldap", "AUTH");
if (param != null)
{
env.put(Context.SECURITY_AUTHENTICATION, param);
}
param = config.getConfigItem("directory", "ldap", "PRINCIPAL");
if (param != null)
{
env.put(Context.SECURITY_PRINCIPAL, param);
}
param = config.getConfigItem("directory", "ldap", "CREDENTIALS");
if (param != null)
{
env.put(Context.SECURITY_CREDENTIALS, param);
}
if (url.toUpperCase().startsWith("LDAPS:"))
{
//TLS connection
//Setup socket factory
env.put("java.naming.ldap.factory.socket",
"com.goldencode.p2j.directory.LdapSocketFactory");
keyStore = config.getConfigItem("directory", "ldap", "KEYSTORE");
trustStore = config.getConfigItem("directory", "ldap", "TRUSTSTORE");
keyPass = config.getConfigItem("directory", "ldap", "KEYPASSWD");
trustPass = config.getConfigItem("directory", "ldap", "TRUSTPASSWD");
alias = config.getConfigItem("directory", "ldap", "ALIAS");
aliasPass = config.getConfigItem("directory", "ldap", "ALIASPASSWD");
if (keyStore == null || keyPass == null || alias == null
|| trustStore == null || trustPass == null || aliasPass == null)
{
throw new ConfigurationException(
"Invalid TLS configuration parameters");
}
LdapSocketFactory.initSocketFactory(keyStore, keyPass, trustStore,
trustPass, alias, aliasPass);
}
try
{
setCtx(new InitialLdapContext(env, null));
}
catch (NamingException e)
{
LOG.severe("", e);
return;
}
}
/**
* No-op implementation.
* <p>
* Current implementation assumes that all external directory changes are
* reloaded automatically. This is correct, but in some operating modes
* this might not be enough because mapping information is not reloaded.
*
* @see com.goldencode.p2j.directory.RamRemapper#refresh()
*/
@Override
public boolean refresh()
{
return true;
}
/**
* Intercept adding of the value to the attribute.
*
* @param node
* A node from which attribute value is requested.
* @param name
* Name of the attribute.
* @param val
* New value which will be added to the attribute.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean addAttributeValueExt(RamNode node, String name,
Object val)
{
if (!addLdapAttributeValue(node, name, val))
{
return false;
}
return super.addAttributeValueExt(node, name, val);
}
/**
* Intercept adding of the node. All parameters are checked and all what we
* need to is to add new node to LDAP and call
* <code>super.addNodeExt()</code>.
*
* @param parent
* A node to which <code>newNode</code> will be added.
* @param newNode
* New node which need to be added to <code>parent</code>.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
protected boolean addNodeExt(RamNode parent, RamNode newNode)
{
if (parent.getRealNode() == null)
{
return false;
}
RamNode ldapNode = addLdapNode(parent, newNode);
if (ldapNode == null)
{
return false;
}
return super.addNodeExt(parent.getRealNode(), ldapNode);
}
/**
* Intercept deletion of the attribute.
*
* @param node
* A node from which attribute value is requested.
* @param name
* Name of the attribute.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
protected boolean deleteNodeAttributeExt(RamNode node, String name)
{
if (!deleteLdapNodeAttribute(node.getRealNode(), name))
{
return false;
}
return super.deleteNodeAttributeExt(node, name);
}
/**
* Intercept deletion of the node attribute value.
*
* @param node
* A node from which attribute value will be deleted.
* @param name
* Name of the attribute.
* @param index
* Attribute value index.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean deleteNodeAttributeValueExt(RamNode node, String name,
int index)
{
if (!deleteLdapNodeAttributeValue(node, name, index))
{
return false;
}
return super.deleteNodeAttributeValueExt(node, name, index);
}
/**
* Intercept removing node.All parameters are checked and all what we need
* to is to remove child node from LDAP and call
* <code>super.deleteNodeExt()</code>.
*
* @param parent
* A node to which <code>newNode</code> will be added.
* @param child
* An ID of the child node to remove.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
protected boolean deleteNodeExt(RamNode parent, String child)
{
if (parent.getRealNode() == null)
{
return false;
}
if (!deleteLdapNode(parent, child))
{
return false;
}
return super.deleteNodeExt(parent.getRealNode(), child);
}
/**
* Load mapping and initialise directory context to access LDAP.
*
* @throws Exception
* Forwarded from various sources and wrapped into
* <code>SchemaException</code>.
*/
protected void load()
throws Exception
{
//Restart connection
try
{
ctx.reconnect(ctx.getConnectControls());
}
catch (NamingException e)
{
LOG.warning("", e);
setCtx(new InitialLdapContext(env, null));
}
if (mode == MODE_URL)
{
//TODO add reading data from URL
return;
}
//Load XML file
if (mode == MODE_FILE)
{
XmlRemapperIO io = new XmlRemapperIO(this, schemaMap);
FileInputStream is = new FileInputStream(mapping);
io.load(is);
is.close();
return;
}
if (mode == MODE_ATTRIBUTE)
{
//Read data from attribute
String[] src = mapping.split("/", 2);
if (src == null || src.length != 2)
{
throw new SchemaException("Invalid mapping location: " + mapping);
}
try
{
Attributes attr = getCtx().getAttributes(src[0], new String[] { src[1] });
String val = (String)attr.get(src[1]).get();
ByteArrayInputStream is;
is = new ByteArrayInputStream(Base64.base64ToByteArray(val));
XmlRemapperIO io = new XmlRemapperIO(this, schemaMap);
io.load(is);
is.close();
}
catch (Exception e)
{
LOG.log(Level.FINER, "", e);
throw new SchemaException("Unable to retrieve mapping data", e);
}
return;
}
if (mode == MODE_SUBTREE)
{
// mapping is a root of the mapping subtree
String[] src = mapping.split("/", 2);
if (src == null || src.length != 2)
{
throw new SchemaException("Invalid mapping location : " + mapping);
}
try
{
Attributes attr = getCtx().getAttributes(src[0], new String[] { src[1] });
int size = attr.get(src[1]).size();
for (int i = 0; i < size; i++)
{
String data = (String)attr.get(src[1]).get(i);
String[] parsed = data.split("/", 4);
if (parsed == null || parsed.length != 4)
{
continue;
}
//Values are mapped so:
//parsed[0] = p2jclass
//parsed[1] = p2jAttribute
//parsed[2] = ldapClass
//parsed[3] = ldapAttribute
schemaMap.mapAttribute(parsed[0], parsed[1], parsed[2],
parsed[3]);
schemaMap.mapLdapClassToP2jClass(parsed[2], parsed[0]);
}
schemaMap.checkRequiredClasses();
}
catch (Exception e)
{
LOG.log(Level.FINER, "", e);
throw new SchemaException("Unable to retrieve schema mapping", e);
}
//Restore schema mapping data
//Build a root node
RamNode r = new RamNode(getObjClass("ldapNode"), "");
r.addStringAttributeValue("location", "");
r.addStringAttributeValue("p2jclass", "container");
setRoot(r);
refreshLdapNode(r);
return;
}
}
/**
* Replacement for the locateNode. It intercepts the result and substitutes
* node with fake node if located node is of special "ldapNode" Object
* Class.
*
* @param id
* Node ID
*
* @return Reference to found node or <code>null</code> if error
* occurred.
*/
protected RamNode locateNode(String id)
{
RamNode node = super.locateNode(id);
if (node == null)
{
return null;
}
if (node.isA("ldapNode"))
{
return extractFromLdap(node);
}
return node;
}
/**
* Intercept moving node.
*
* @param parent
* A parent node of the node which is about to be moved to new
* location.
* @param name
* Name of the node which will be moved.
* @param newParent
* A node which will be new parent node if operation will be
* successful.
* @param newName
* New name of the node.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean moveNodeExt(RamNode parent, String name,
RamNode newParent, String newName)
{
if (newParent.getRealNode() == null ||
parent.getChild(name) == null ||
!parent.getChild(name).isA("ldapNode"))
{
return false;
}
if (!moveLdapNode(parent, name, newParent.getRealNode(), newName))
{
return false;
}
if (parent.getRealNode() != null)
{
parent = parent.getRealNode();
}
return super.moveNodeExt(parent, name, newParent.getRealNode(), newName);
}
/**
* Save current tree into XML file.
*
* @throws Exception
* If I/O error occurred during operation or
* <code>XmlHelper</code> throws an
* <code>ParserConfigurationException</code>.
*/
protected void save()
throws Exception
{
if (mode == MODE_URL || mode == MODE_SUBTREE)
{
return;
}
if (mode == MODE_FILE)
{
XmlRemapperIO io = new XmlRemapperIO(this, schemaMap);
io.save(new FileOutputStream(mapping));
return;
}
if (mode == MODE_ATTRIBUTE)
{
String[] src = mapping.split("/", 2);
if (src == null || src.length != 2)
{
throw new SchemaException("Invalid mapping location " + mapping);
}
XmlRemapperIO io = new XmlRemapperIO(this, schemaMap);
ByteArrayOutputStream os = new ByteArrayOutputStream();
io.save(os);
os.close();
String val = Base64.byteArrayToBase64(os.toByteArray());
try
{
Attributes attrs;
attrs = getCtx().getAttributes(src[0], new String[] { src[1] });
try
{
if (val.equals(attrs.get(src[1]).get(0))) //No changes
{
return;
}
attrs.get(src[1]).set(0, val);
}
catch (Exception e)
{
throw new SchemaException("Unable to update mapping", e);
}
getCtx().modifyAttributes(src[0], DirContext.REPLACE_ATTRIBUTE, attrs);
}
catch (Exception e)
{
throw new SchemaException("Unable to update mapping", e);
}
return;
}
}
/**
* Intercept changing node attribute value.
*
* @param node
* A node which contains the attribute to change.
* @param name
* Attribute name.
* @param index
* Index of the value to retrieve.
* @param val
* New value for the attribute variable.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean setNodeValueExt(RamNode node, String name, Object val,
int index)
{
if (!setLdapAttributeValue(node, name, val, index))
{
return false;
}
return node.setAttributeValue(name, val, index);
}
/**
* Add value to the LDAP node attribute.
*
* @param node
* A reference to the <code>RamNode</code> of "ldapNode" object
* class.
* @param name
* Attribute name.
* @param val
* New attribute value.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
private boolean addLdapAttributeValue(RamNode node, String name, Object val)
{
if (node == null)
{
return false;
}
String location = getLocation(node.getRealNode());
String attribute = getLdapAttribute(node.getRealNode(), name);
Attribute tmp = node.genAttribute(name);
if (location == null || tmp == null)
{
return false;
}
val = tmp.getStringValue(val);
if (tmp.isA(AttributeType.ATTR_STRING) && val.equals(""))
{
val = EMPTY;
}
if (!tmp.getDefinition().isMultiple())
{
try
{
Attributes tmpAttrs = getCtx().getAttributes(location, new String[]
{
attribute
});
if (tmpAttrs.get(attribute) != null && tmpAttrs.get(attribute).size() > 0)
{
//Multiple values are not allowed.
return false;
}
}
catch (Exception e)
{
//Attribute is missing, so we can add it safely.
LOG.log(Level.FINER, "Attribute is missing.", e);
}
}
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(attribute, val));
try
{
getCtx().modifyAttributes(location, mods);
}
catch (NamingException e)
{
LOG.info("", e);
return false;
}
return true;
}
/**
* Add new node to LDAP tree.
*
* @param node
* <code>RamNode</code> instance of "ldapNode" Object Class
* which contains mapping information.
* @param newNode
* New node to add.
*
* @return New RamNode instance of "ldapNode" object class or
* <code>null</code>
*/
private RamNode addLdapNode(RamNode node, RamNode newNode)
{
// Can't add a node if there is no backing LDAP node
if (node == null || newNode == null)
{
return null;
}
String location = getLocation(node.getRealNode());
if (location == null)
{
return null;
}
RamNode fakeNode = new RamNode(getObjClass("ldapNode"),
newNode.getName(), null);
try
{
String p2jClass = newNode.getNodeClass().getName();
String objClass = schemaMap.getLdapClass(p2jClass);
String name = composeName(location, newNode.getName(), p2jClass);
if (objClass == null)
{
throw new IncompatibleClassChangeError("No LDAP class for " + p2jClass);
}
if (name == null)
{
throw new IncompatibleClassChangeError("Unable to compose name for class " + p2jClass);
}
Attributes attributes = new BasicAttributes(true);
BasicAttribute attribute = new BasicAttribute("objectClass", objClass);
attributes.put(attribute);
String[] attrs = schemaMap.getLdapAttributes(objClass);
if (attrs != null)
{
for (int i = 0; i < attrs.length; i++)
{
BasicAttribute newAttr = new BasicAttribute(attrs[i]);
String attrName = schemaMap.getP2jAttribute(p2jClass, attrs[i]);
Attribute attr = newNode.getAttribute(attrName);
if (attr != null)
{
String[] values = attr.getValues();
for (int j = 0; j < attr.getCount(); j++)
{
if (attr.isA(AttributeType.ATTR_STRING) && values[j].isEmpty())
{
newAttr.add(EMPTY);
}
else
{
newAttr.add(values[j]);
}
}
attributes.put(newAttr);
}
}
}
if (!fakeNode.addAttributeValue("location", name, true))
{
return null;
}
if (!fakeNode.addAttributeValue("p2jclass", p2jClass, true))
{
return null;
}
if (!fakeNode.isValid())
{
return null;
}
getCtx().createSubcontext(name, attributes);
}
catch (Exception e)
{
LOG.info("", e);
return null;
}
return fakeNode;
}
/**
* Compose full name for the given location and node name.
*
* @param location
* Starting location.
* @param name
* Node name.
* @param p2jClass
* An P2J Object Class name for the given node name.
*
* @return Full location string.
*/
private String composeName(String location, String name, String p2jClass)
{
String prefix = schemaMap.getLdapAttribute(p2jClass, "objectName");
if (prefix == null)
{
return null;
}
if (location == null || location.isEmpty())
{
return prefix + "=" + name;
}
return prefix + "=" + name + "," + location;
}
/**
* Delete node in LDAP directory.
*
* @param node
* A node of "ldapNode" object class.
* @param child
* Name of the child node to remove.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
private boolean deleteLdapNode(RamNode node, String child)
{
if (node == null || child == null)
{
return false;
}
String name = getLocation(node.getChild(child));
if (name == null)
{
return false;
}
try
{
getCtx().destroySubcontext(name);
}
catch (NamingException e)
{
LOG.info("", e);
return false;
}
return true;
}
/**
* Remove attribute from the LDAP node.
*
* @param node
* A node of "ldapNode" Object Class.
* @param name
* Attribute name.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
private boolean deleteLdapNodeAttribute(RamNode node, String name)
{
if (node == null || name == null)
{
return false;
}
String location = getLocation(node);
String attribute = getLdapAttribute(node, name);
if (location == null || attribute == null)
{
return false;
}
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
new BasicAttribute(attribute));
try
{
getCtx().modifyAttributes(location, mods);
}
catch (NamingException e)
{
LOG.log(Level.FINER, "", e);
return false;
}
return true;
}
/**
* Delete node attribute value from LDAP node.
*
* @param node
* A node of "ldapNode" Object Class.
* @param name
* Attribute name.
* @param index
* Attribute value index.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
private boolean deleteLdapNodeAttributeValue(RamNode node,
String name, int index)
{
if (node == null || name == null || index < 0)
{
return false;
}
String location = getLocation(node.getRealNode());
String attribute = getLdapAttribute(node.getRealNode(), name);
Attribute tmp = node.genAttribute(name);
if (location == null || attribute == null || tmp == null)
{
return false;
}
try
{
Attributes attrs = getCtx().getAttributes(location, new String[] {attribute});
if (attrs.get(attribute) == null) //No such attribute
{
return false;
}
attrs.get(attribute).remove(index);
if (attrs.get(attribute).size() != 0)
{
getCtx().modifyAttributes(location, DirContext.REPLACE_ATTRIBUTE, attrs);
}
else
{
if(tmp.getDefinition().isMandatory())
{
return false;
}
getCtx().modifyAttributes(location, DirContext.REMOVE_ATTRIBUTE, attrs);
}
}
catch (Exception e)
{
LOG.info("", e);
return false;
}
return true;
}
/**
* Extract information from LDAP.
*
* @param node
* Source node of "ldapNode" Object Class.
*
* @return fake RamNode which is used in further operations instead of real
* node of "ldapNode" Object Class.
*/
private RamNode extractFromLdap(RamNode node)
{
String location = getLocation(node);
String p2jClass = getP2jClass(node);
String ldapClass = schemaMap.getLdapClass(p2jClass);
if (location == null || p2jClass == null || ldapClass == null)
{
return null;
}
// Construct a node from known data;
RamNode fakeNode = new RamNode(getObjClass(p2jClass), node.getName(), node);
String[] attrNames = schemaMap.getLdapAttributes(ldapClass);
if (attrNames != null)
{
try
{
Attributes answer;
answer = getCtx().getAttributes(location, attrNames);
NamingEnumeration<? extends javax.naming.directory.Attribute> ae =
answer.getAll();
while(ae.hasMore())
{
javax.naming.directory.Attribute attr = ae.next();
String p2jName = schemaMap.getP2jAttribute(p2jClass, attr.getID());
if (p2jName == null)
{
continue;
}
// Create an attribute
Attribute p2jAttribute = fakeNode.genAttribute(p2jName);
if (p2jAttribute == null)
{
continue;
}
// Retrieve all values
for (NamingEnumeration<?> e = attr.getAll(); e.hasMore();)
{
Object obj = e.next();
if (p2jAttribute.isA(AttributeType.ATTR_STRING) && obj.equals(EMPTY))
{
obj = "";
}
if (!fakeNode.addStringAttributeValue(p2jName, (String)obj))
{
return null;
}
}
}
}
catch (Exception e)
{
LOG.info("", e);
return null;
}
}
//Check if node need a refresh.
refreshLdapNode(node);
RamNode[] list = node.getChildList();
for (int i = 0; i < list.length; i++)
{
if (!fakeNode.addChild(list[i]))
{
return null;
}
}
if (!fakeNode.isValid())
{
return null;
}
return fakeNode;
}
/**
* Get JNDI LDAP context.
*
* @return Returns the context.
*/
private LdapContext getCtx()
{
return ctx;
}
/**
* Find a name of the LDAP attribute which corresponds to specified P2J
* attribute.
*
* @param node
* <code>RamNode</code> of "ldapNode" Object Class.
* @param name
* Name of P2J attribute.
*
* @return Name of the LDAP attribute.
*/
private String getLdapAttribute(RamNode node, String name)
{
return schemaMap.getLdapAttribute(getP2jClass(node), name);
}
/**
* Get LDAP Object Class Name for child node of specified location.
*
* @param location
* LDAP node location.
* @param name
* Name of child node of specified location.
*
* @return LDAP Object Class name for specified node.
*/
private String getLdapNodeClass(String location, String name)
{
if (!location.isEmpty())
{
name = name + "," + location;
}
try
{
Attributes answer = getCtx().getAttributes(name, new String[] {"objectClass"});
return (String)answer.get("objectClass").get();
}
catch (NamingException e)
{
LOG.info("", e);
}
return null;
}
/**
* Convenience method to retrieve information about LDAP context name
* (location) from specified <code>RamNode</code> instance of "ldapNode"
* Object Class.
*
* @param node
* Reference to the RamNode of "ldapNode" Object Class.
*
* @return LDAP context name.
*/
private String getLocation(RamNode node)
{
if (node == null || !node.isA("ldapNode"))
{
return null;
}
Attribute location = node.getAttribute("location");
if (location == null)
{
return null;
}
return (String) location.getValue(0);
}
/**
* Get name of P2J Object Class associated with node of "ldapNode"
* Object Class.
*
* @param node
* <code>RamNode</code> of "ldapNode" Object Class.
*
* @return Name of the LDAP Object Class.
*/
private String getP2jClass(RamNode node)
{
if (node == null || !node.isA("ldapNode"))
{
return null;
}
Attribute p2jClassAttr = node.getAttribute("p2jclass");
if (p2jClassAttr == null || p2jClassAttr.getValues() == null)
{
return null;
}
return (String)p2jClassAttr.getValue(0);
}
/**
* Move LDAP node to the new location.
*
* @param parent
* Parent node.
* @param name
* Node name.
* @param newParent
* Target parent node.
* @param newName
* Target name.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
private boolean moveLdapNode(RamNode parent, String name,
RamNode newParent, String newName)
{
RamNode node = parent.getChild(name);
String location = getLocation(node);
String newLocation = getLocation(newParent);
if (location == null || newLocation == null)
{
return false;
}
try
{
Attribute clsAttr = node.getAttribute("p2jclass");
String newLoc = composeName(newLocation, newName,
clsAttr.getStringValue(0));
getCtx().rename(location, newLoc);
//Change location
Attribute locAttr = node.getAttribute("location");
locAttr.setValue(0, newLoc);
}
catch (NamingException e)
{
LOG.info("", e);
return false;
}
return true;
}
/**
* Compare existing information about child nodes with stored in mapping
* and refresh mapping of necessary.
*
* @param node
* Node to refresh.
*/
private void refreshLdapNode(RamNode node)
{
if (node == null || !node.isA("ldapNode"))
{
return;
}
String location = getLocation(node);
try
{
NamingEnumeration<NameClassPair> list = getCtx().list(location);
Map<String, String> names = new HashMap<>();
Map<String, String> classes = new HashMap<>();
while (list.hasMore())
{
NameClassPair nc = list.next();
String name = nc.getName();
String[] lst = name.split("=", 2);
if (lst == null || lst.length != 2)
{
continue;
}
String ldapClass = getLdapNodeClass(location, name);
if (ldapClass == null)
{
continue;
}
names.put(lst[1], name);
classes.put(lst[1], ldapClass);
}
//Add missing child nodes
Set<String> childNames = new TreeSet<>(names.keySet());
for (Iterator<String> iter = childNames.iterator(); iter.hasNext();)
{
String name = iter.next();
if (node.getChild(name) == null)
{
//Add node
RamNode child = new RamNode(getObjClass("ldapNode"), name, null);
String ldapClass = classes.get(name);
if (ldapClass == null)
{
continue;
}
String p2jClass = schemaMap.getP2jClass(ldapClass);
if (p2jClass == null)
{
continue;
}
String childLoc = names.get(name);
if (!location.isEmpty())
{
childLoc = childLoc + "," + location;
}
do
{
if (!child.addAttributeValue("location", childLoc, true))
{
break;
}
if (!child.addAttributeValue("p2jclass", p2jClass, true))
{
break;
}
node.addChild(child);
refreshLdapNode(child);
}
while (false);
}
//if (recursive)
// refreshLdapNode(node.getChild(name), recursive);
}
//Now iterate over existing nodes and if node is not in names
//and its type is "ldapNode" then remove it.
RamNode[] childNodes = node.getChildList();
for (int i = 0; i < childNodes.length; i++)
{
String name = childNodes[i].getName();
if (names.get(name) == null && childNodes[i].isA("ldapNode"))
{
node.removeChild(name);
}
}
}
catch (Exception e)
{
LOG.info("", e);
return;
}
}
/**
* Set JNDI LDAP context.
*
* @param ctx
* The context to set.
*/
private void setCtx(LdapContext ctx)
{
this.ctx = ctx;
}
/**
* Set value of the attribute of LDAP node.
*
* @param node
* A node which contains the attribute to change.
* @param name
* Attribute name.
* @param index
* Index of the value to retrieve.
* @param val
* New value for the attribute variable.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
private boolean setLdapAttributeValue(RamNode node, String name,
Object val, int index)
{
String location = getLocation(node.getRealNode());
String attribute = getLdapAttribute(node.getRealNode(), name);
Attribute tmp = node.genAttribute(name);
if (location == null || attribute == null || tmp == null)
{
return false;
}
val = tmp.getStringValue(val);
if (tmp.isA(AttributeType.ATTR_STRING) && val.equals(""))
{
val = EMPTY;
}
try
{
Attributes attrs = getCtx().getAttributes(location, new String[]
{
attribute
});
try
{
if (index >= attrs.get(attribute).size())
{
return false;
}
if (!val.equals(attrs.get(attribute).get(index)))
attrs.get(attribute).set(index, val);
}
catch (Exception e)
{
return false;
}
getCtx().modifyAttributes(location, DirContext.REPLACE_ATTRIBUTE,
attrs);
}
catch (Exception e)
{
LOG.info("", e);
return false;
}
return true;
}
}