LdapMapGen.java
/*
** Module : LdapMapGen.java
** Abstract : Mapping generator for LDAP back-end.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SIY 20050404 @21015 Created initial version
** 002 SIY 20050505 @21195 Moved method upcaseFirst() to IdUtils,
** updated documentation, compressed imports.
** 003 GES 20090515 @42207 Imports change.
** 004 GES 20101117 Switched to new static schema loader that reads
** from well-known resources rather than a manually
** defined configuration file.
** 005 TJD 20220504 Java 11 compatibility minor changes
** 006 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 007 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.security.InvalidParameterException;
import java.util.*;
import java.util.logging.*;
import javax.naming.*;
import javax.naming.directory.*;
import com.goldencode.p2j.util.logging.*;
import org.w3c.dom.*;
import com.goldencode.util.*;
import com.goldencode.p2j.cfg.ConfigurationException;
/**
* The design of the LDAP back-end allows different modes of operation but all
* of them based on the mapping information collected in some way. The mapping
* information tells LDAP back-end how to represent LDAP server to P2J
* DirectoryService users.
* <p>
* The mapping contains two main parts: <b>schema-level </b> mapping and
* <b>node-level </b> mapping. The <b>schema-level </b> mapping defines how
* particular LDAP object class and its attributes corresponds to P2J object
* classes and attributes. The <b>node-level </b> mapping defines which LDAP
* node represents particular node in P2J directory.
* <p>
* The LDAP back-end supports two major modes of operation: when only
* schema-level mapping is defined and when both schema-level and node-level
* mapping are provided. The difference between them is that first one assumes
* that LDAP server already provides hierarchy suitable for use by P2J and
* LDAP back-end just uses it as is. In second mode there is no need to have
* such a hierarchy, each P2J node can be individually mapped into LDAP node
* and some nodes may not have corresponding LDAP node at all.
* <p>
* Since writing mapping information manually is complicated and error prone
* procedure, <code>LdapMapGen</code> utility is provided to simplify it.
* The main purpose of the <code>LdapMapGen</code> utility is to generate
* mapping information required for use of P2J DirectoryService subsystem with
* LDAP server. The resulting mapping information can be stored in the map
* file in XML format recognized by LDAP back-end or at the LDAP server in
* specified location. Beside that, utility in some cases can generate schema
* file suitable for use with LDAP server.
*/
public class LdapMapGen
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(LdapMapGen.class);
/** One-to-one mapping between LDAP and P2J directories */
private static final int MAP_SUBTREE = 1;
/** Node-to-node mapping */
private static final int MAP_NODE = 2;
/** Command line parameters */
private String[] args;
/**
* Mapping between LDAP and P2J nodes as it defined in configuration file.
*/
private Map<String, String> configNodeList = new CaseInsensitiveHashMap<>();
/** LDAP directory context */
private DirContext context = null;
/** Configuration parameter: LDAP credentials */
private String ldapCredentials = null;
/** Configuration parameter: LDAP principal */
private String ldapPrincipal = null;
/** Configuration parameter: LDAP URL */
private String ldapUrl = null;
/** Mapping data */
private SchemaMapping mapping = new SchemaMapping();
/** Mapping mode between LDAP and P2J */
private int mappingMode = MAP_SUBTREE;
/** Complete expanded mapping between P2J and LDAP nodes */
private Map<String, String> nodeList = new CaseInsensitiveHashMap<>();
/** Heading text for the LDAP schema*/
private String ldapSchemaHeader;
/** Root of the mapping if saving to subtree is desired */
private String mappingNode = null;
/** LDAP node attribute name where mapping will be stored */
private String mapAttribute = null;
/** LDAP object class of the node where mapping will be stored */
private String mapObjClass = null;
/**
* Make LDAP attribute name from object class name and attribute name.
*
* @param cls
* Object class name.
* @param attr
* Attribute name.
*
* @return LDAP attribute name.
*/
private static String mkAttrName(String cls, String attr)
{
return "gcd" + IdUtils.upcaseFirst(cls) + IdUtils.upcaseFirst(attr);
}
/**
* Sort array of strings.
*
* @param src
* Source array.
*
* @return Sorted array.
*/
private static String[] sortArray(String[] src)
{
if (src == null)
{
return null;
}
if (src.length == 0)
{
return src;
}
Map<String, String> map = new HashMap<>();
for (int i = 0; i < src.length; i++)
{
map.put(src[i], src[i]);
}
Set<String> set = new TreeSet<>(map.keySet());
String[] res = new String[src.length];
int i = 0;
for (Iterator<String> iter = set.iterator(); iter.hasNext(); i++)
{
res[i] = new String(iter.next());
}
return res;
}
/**
* Build list of attributes from the vector.
*
* @param req
* Vector with attribute names.
*
* @return String containing attribute names from the vector.
*/
private static String vec2str(List<String> req)
{
if (req.size() == 1)
{
return req.get(0);
}
StringBuilder res = new StringBuilder("(");
for (int i = 0; i < req.size(); i++)
{
res.append(req.get(i));
if (i != (req.size() - 1))
{
res.append(" $ ");
}
}
return res + ")";
}
/**
* Shortcut for the System.out.prinln()
*
* @param msg
* Message to print to standard output.
*/
static void say(String msg)
{
System.out.println(msg);
}
/**
* Convert string into boolean value. Only string which contains text
* "true" (case does not matter) is converted into boolean
* <code>true</code>
*
* @param str
* Source string.
*
* @return <code>boolean</code> value for given string.
*/
static boolean str2bool(String str)
{
return Boolean.valueOf(str).booleanValue();
}
/**
* Construct an instance for specified command line parameters.
*
* @param args
* Command line parameters passed to main()
*/
LdapMapGen(String[] args)
{
this.args = args;
}
/**
* Create node of "ldapNode" type for specified P2J node and class.
*
* @param remapper
* XML back-end reference.
* @param nodeId
* P2J node ID.
*
* @throws NamingException
* Forwarded from <code>getNodeClass()</code>.
*/
void createNode(RamRemapper remapper, String nodeId)
throws NamingException
{
String ldapClass = getNodeClass(nodeId);
String p2jClass = mapping.getP2jClass(ldapClass);
String cn = nodeList.get(nodeId);
if (p2jClass == null)
{
say("No mapping defined for the object class " + ldapClass + " found in node " + nodeId);
return;
}
//say("Creating node for class " + ldapClass);
if (mappingMode != MAP_SUBTREE)
{
//Check presence of intermediate nodes
String[] paths = IdUtils.listPaths(nodeId);
for (int i = 0; paths != null && i < paths.length; i++)
{
if (remapper.getNodeClassName(paths[i]) == null)
{
remapper.addNode(paths[i], "container", null, null);
}
}
}
//say("P2J address = " + nodeId);
boolean rc = true;
if (nodeId.equals("/") && mappingMode != MAP_SUBTREE)
{
replaceRoot(remapper, cn, p2jClass);
}
else
{
rc = remapper.addNode(nodeId, "ldapNode",
new String[] {"location", "p2jclass"},
new String[] {cn, p2jClass});
}
if (!rc)
{
say("Creating of node [id:" + nodeId + ", location: " + cn + " class: " + ldapClass + "] failed");
}
}
/**
* Expand all subtree to subtree mappings into explicit node-to-node
* mappings.
*
* @throws NamingException
* Forwarded from <code>expandWildcard()</code> method.
*/
void expandNodeMappings()
throws NamingException
{
String[] keys;
keys = configNodeList.keySet().toArray(new String[0]);
for (int i = 0; i < keys.length; i++)
{
if (keys[i].endsWith("*"))
{
//say("Expanding " + keys[i]);
expandWildcard(nodeList, keys[i], configNodeList.get(keys[i]));
}
else
{
//say("Adding " + keys[i]);
nodeList.put(configNodeList.get(keys[i]), keys[i]);
}
}
}
/**
* Expand subtree node ID with wildcards and return a list of all nodes in
* the subtree.
*
* @param result
* Storage for the node to node mapping.
* @param cn
* LDAP node ID.
* @param nodeId
* P2J node ID
*
* @throws NamingException
* Forwarded from the JNDI methods.
*/
void expandWildcard(Map<String, String> result, String cn, String nodeId)
throws NamingException
{
//Strip mask
if (cn.endsWith("*"))
{
cn = cn.substring(0, cn.length() - 1);
}
expandWorker(result, prepareContext(), cn, nodeId);
}
/**
* Recursive worker routine for the <code>expandWildcard()</code> method.
*
* @param result
* Vector with collected nodes.
* @param ctx
* LDAP Directory context to scan.
* @param cn
* Node ID to start scan from.
* @param nodeId
* P2J node ID.
*
* @throws NamingException
* Forwarded from <code>DirContext.list()</code> method.
*/
void expandWorker(Map<String, String> result, DirContext ctx, String cn, String nodeId)
throws NamingException
{
NamingEnumeration<NameClassPair> list = ctx.list(cn);
if (cn.length() > 0)
{
result.put(nodeId, cn);
}
while (list.hasMore())
{
NameClassPair nc = list.next();
String name = nc.getName();
String[] lst = name.split("=", 2);
if (lst == null || lst.length != 2)
{
say("Unable to parse LDAP node name " + name + " at " + cn);
continue;
}
String newId = nodeId + "/" + lst[1];
if (cn.length() > 0)
{
name = name + "," + cn;
}
expandWorker(result, ctx, name, newId);
}
}
/**
* Return LDAP Object Class name for specified P2J node.
*
* @param nodeId
* LDAP node ID.
* @return Object Class for specified LDAP node.
*
* @throws NamingException
* Forwarded from the JNDI classes.
*/
String getNodeClass(String nodeId)
throws NamingException
{
String cn = nodeList.get(nodeId);
if (cn == null)
{
return null;
}
Attributes answer;
answer = prepareContext().getAttributes(cn, new String[]
{
"objectClass"
});
return (String)answer.get("objectClass").get();
}
/**
* Load map configuration file.
*
* @param file
* Source file name.
* @throws Exception
* Forwarded from the various classes used my method.
*/
void loadConfig(String file)
throws Exception
{
Document dom = XmlHelper.parse(new File(file));
Element elem = dom.getDocumentElement();
Node rootNode = elem.getFirstChild();
if (rootNode == null)
{
throw new ConfigurationException("Invalid configuration file" + file);
}
while (rootNode != null)
{
if (rootNode.getNodeName().equals("config"))
{
elem = (Element)rootNode;
String type = elem.getAttribute("mapping-mode");
if ("subtree".equalsIgnoreCase(type) ||
"direct".equalsIgnoreCase(type))
{
mappingMode = MAP_SUBTREE;
}
else if ("node".equalsIgnoreCase(type))
{
mappingMode = MAP_NODE;
}
else
{
say("Unknown mapping mode [" + type + "]");
}
ldapPrincipal = elem.getAttribute("principal");
ldapCredentials = elem.getAttribute("credentials");
ldapUrl = elem.getAttribute("url");
// this element is no longer supported, instead the standard
// schema and any schema extensions will be loaded as is done in
// the normal server startup
// schemaFile = elem.getAttribute("p2j-schema-file");
ldapSchemaHeader = elem.getAttribute("ldap-schema-header");
mappingNode = elem.getAttribute("mapping-destination");
mapAttribute = elem.getAttribute("mapping-attribute");
mapObjClass = elem.getAttribute("mapping-object-class");
}
else if (rootNode.getNodeName().equals("node-mapping"))
{
NodeList attr = rootNode.getChildNodes();
for (int i = 0; i < attr.getLength(); i++)
{
if (attr.item(i).getNodeName().equals("node"))
{
elem = (Element)attr.item(i);
String ldap = elem.getAttribute("ldap");
String p2j = elem.getAttribute("p2j");
if (ldap == null || p2j == null)
{
say("Invalid mapping [" + ldap + "<->" + p2j
+ "] ignored");
continue;
}
configNodeList.put(ldap, p2j);
}
}
}
else if (rootNode.getNodeName().equals("class-mapping"))
{
NodeList attr = rootNode.getChildNodes();
for (int i = 0; i < attr.getLength(); i++)
{
if (attr.item(i).getNodeName().equals("class"))
{
Element attrElem = (Element)attr.item(i);
String ldapClass = attrElem.getAttribute("ldap");
String p2jClass = attrElem.getAttribute("p2j");
if (ldapClass == null || p2jClass == null)
{
say("Invalid mapping [" + ldapClass + "<->" + p2jClass + "] ignored");
continue;
}
if (ldapClass.isEmpty() && p2jClass.isEmpty())
{
continue;
}
mapping.mapP2jClassToLdap(p2jClass, ldapClass);
NodeList attrMap = elem.getChildNodes();
for (int j = 0; j < attrMap.getLength(); j++)
{
if (attrMap.item(j).getNodeName().equals("attribute"))
{
Element mapElem = (Element)attrMap.item(j);
String ldapAttr = mapElem.getAttribute("ldap");
String p2jAttr = mapElem.getAttribute("p2j");
if (ldapAttr == null || p2jAttr == null)
{
say("Invalid mapping [" + ldapClass + "." + ldapAttr +
"<->" + p2jClass + "." + p2jAttr + "] ignored");
continue;
}
if (ldapAttr.isEmpty() || p2jAttr.isEmpty())
continue;
mapping.mapAttribute(p2jClass, p2jAttr, ldapClass,
ldapAttr);
}
}
}
}
}
rootNode = rootNode.getNextSibling();
}
}
/**
* Prepare DirContext using LDAP configuration parameters obtained from
* configuration file.
*
* @return Prepared LDAP context.
* @throws NamingException
* Forwarded from <code>InitialDirContext()</code>.
*/
DirContext prepareContext()
throws NamingException
{
if (context != null)
return context;
Hashtable<String,String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, ldapPrincipal);
env.put(Context.SECURITY_CREDENTIALS, ldapCredentials);
context = new InitialDirContext(env);
return context;
}
/**
* Main application worker.
*/
void run()
{
//Parse arguments
//Assume first parameter is file name
String configFile = null;
String outputFile = null;
String ldapSchema = null;
boolean updateLdap = false;
for (int i = 0; i < args.length; i++)
{
if (i == 0)
{
configFile = args[0];
continue;
}
if (args[i].equalsIgnoreCase("-o"))
{
if (args.length > (i + 1))
outputFile = args[++i];
continue;
}
if (args[i].equalsIgnoreCase("-s"))
{
if (args.length > (i + 1))
ldapSchema = args[++i];
continue;
}
if (args[i].equalsIgnoreCase("-u"))
{
updateLdap = true;
outputFile = "";
continue;
}
}
if (configFile == null || (outputFile == null && ldapSchema == null))
{
say("Usage: java LdapMapGen <source_map.xml> "
+ " [-o <output_map.xml> | -u]"
+ " [-s <ldap.schema>]");
return;
}
try
{
loadConfig(configFile);
SchemaLoad.initSchema();
//Check if SchemaMapping contains anything
String[] list = mapping.getMapList();
if (list == null || list.length == 0)
selfMap();
else
{
if (ldapSchema != null)
say("Warning: generation of LDAP schema is supported " +
"only from P2J schema file. Request ignored.");
}
mapping.checkRequiredClasses();
if (updateLdap)
{
say("Writing schema mapping to " + mappingNode);
writeLdap();
say("Done.");
}
else
{
if (outputFile != null)
{
if (mappingMode == MAP_SUBTREE)
configNodeList.put("*", "");
expandNodeMappings();
say("Writing map to " + outputFile);
writeMap(outputFile);
say("Done.");
}
}
if (ldapSchema != null)
{
say("Writing LDAP schema to " + ldapSchema);
writeLdapSchema(ldapSchema);
say("Done.");
}
}
catch (Exception e)
{
if (e instanceof FileNotFoundException)
throw new InvalidParameterException(e.getMessage());
LOG.log(Level.FINE, "", e);
}
}
/**
* Write resulting XML map file.
*
* @param file
* XML file name.
* @throws Exception
* Forwarded from <code>createNode()</code> method.
*/
void writeMap(String file)
throws Exception
{
RamRemapper remapper = new RamRemapper();
remapper.bind();
Set<String> nodes = new TreeSet<>(nodeList.keySet());
for (Iterator<String> iter = nodes.iterator(); iter.hasNext();)
{
String nodeId = iter.next();
createNode(remapper, nodeId);
}
XmlRemapperIO io = new XmlRemapperIO(remapper, mapping);
//Build root node manually
if (mappingMode == MAP_SUBTREE)
{
replaceRoot(remapper, "", "container");
}
io.save(new FileOutputStream(file));
remapper.unbind();
}
/**
* Replace root node with one of type "ldapNode".
*
* @param remapper
* Back-end to perform operation on.
* @param cn
* LDAP node location.
* @param p2jClass
* Name of the P2J Object Class.
*/
private void replaceRoot(RamRemapper remapper, String cn, String p2jClass)
{
SchemaStorage schema = SchemaStorage.getSchema();
RamNode r = new RamNode(schema.getObjectClass("ldapNode"), "");
r.addStringAttributeValue("location", cn);
r.addStringAttributeValue("p2jclass", p2jClass);
RamNode[] list = remapper.getRoot().getChildList();
for (int i = 0; i < list.length; i++)
{
r.addChild(list[i]);
}
remapper.setRoot(r);
}
/**
* Write LDAP schema file into
*
* @param outFile
* File name where LDAP schema will be saved.
*/
private void writeLdapSchema(String outFile)
{
try
{
PrintStream out = new PrintStream(new FileOutputStream(outFile));
//Output header
if (ldapSchemaHeader != null)
{
out.println(ldapSchemaHeader);
}
int order = 100;
SchemaStorage schema = SchemaStorage.getSchema();
String[] classList = sortArray(schema.getClassNames());
for (int i = 0; i < classList.length; i++)
{
ObjectClass objClass = schema.getObjectClass(classList[i]);
if (objClass.getName().equals("metaclass"))
{
continue;
}
AttributeDefinition[] attrs = objClass.getClassDefinition();
for (int j = 0; attrs != null && j < attrs.length; j++)
{
String[] typeStr =
{
"unknown" , "Integer" , "Boolean" ,
"String" , "Double" , "ByteArray",
"BitField", "BitSelector", "Date" ,
"Time"
};
//Skip Double, there is no appropriate mapping for it yet.
//To-Do.
if (attrs[j].getType() == AttributeType.ATTR_DOUBLE)
{
continue;
}
String type = typeStr[attrs[j].getType()];
if (attrs[j].isMultiple())
{
type += 's';
}
out.println("attributetype ( GCD.P2J.At:" + order+ " NAME " +
"'" + mkAttrName(objClass.getName(), attrs[j].getName()) +
"' SUP gcd" + type + ")");
order++;
}
}
out.println("\n#---- object classes --------------------------------------------------------");
out.println();
order = 1;
for (int i = 0; i < classList.length; i++)
{
ObjectClass objClass = schema.getObjectClass(classList[i]);
String name = objClass.getName();
if (name.equals("metaclass"))
{
continue;
}
String cn = "cn";
if (name.equals("user"))
{
cn = "uid";
}
out.println();
out.println("# " + name);
out.println("objectclass ( GCD.P2J.OC:" + order + " NAME 'gcd" + IdUtils.upcaseFirst(name) +
"'\n" + " DESC 'P2J " + name + " class'\n" + " SUP top STRUCTURAL");
order++;
AttributeDefinition[] attrs = objClass.getClassDefinition();
List<String> opt = new ArrayList<>();
List<String> req = new ArrayList<>();
req.add(cn);
for (int j = 0; attrs != null && j < attrs.length; j++)
{
String attr = mkAttrName(name, attrs[j].getName());
if (attrs[j].isMandatory())
{
req.add(attr);
}
else
{
opt.add(attr);
}
}
String list = vec2str(req);
if (opt.size() == 0)
{
out.println(" MUST " + list + ")");
}
else
{
out.println(" MUST " + list);
list = vec2str(opt);
out.println(" MAY " + list + ")");
}
out.println();
}
out.println("## end of schema");
out.close();
}
catch(Exception e)
{
say("Error writing mapping file.");
LOG.log(Level.FINE, "", e);
}
}
/**
* Generate mapping using only P2J schema data.
*/
private void selfMap()
{
SchemaStorage schema = SchemaStorage.getSchema();
String[] classList = sortArray(schema.getClassNames());
for (int i = 0; i < classList.length; i++)
{
ObjectClass objClass = schema.getObjectClass(classList[i]);
String p2jClass = objClass.getName();
if (p2jClass.equals("metaclass"))
{
continue;
}
String cn = "cn";
String ldapClass = "gcd" + IdUtils.upcaseFirst(p2jClass);
if (p2jClass.equals("user"))
{
cn = "uid";
}
mapping.mapLdapClassToP2jClass(ldapClass, p2jClass);
mapping.mapAttribute(p2jClass, "objectName", ldapClass, cn);
AttributeDefinition[] attrs = objClass.getClassDefinition();
for (int j = 0; attrs != null && j < attrs.length; j++)
{
mapping.mapAttribute(p2jClass, attrs[j].getName(), ldapClass,
mkAttrName(p2jClass, attrs[j].getName()));
}
}
}
/**
* Write mapping into LDAP node/attribute.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void writeLdap()
throws ConfigurationException
{
if (mappingNode == null)
{
throw new ConfigurationException("LDAP mapping node is not defined");
}
if (mapAttribute == null)
{
throw new ConfigurationException("LDAP mapping node attribute is not defined");
}
if (mapObjClass == null)
{
throw new ConfigurationException("LDAP mapping node object class is not defined");
}
try
{
DirContext ctx = prepareContext();
Attributes attrs = new BasicAttributes(true);
BasicAttribute attr = new BasicAttribute("objectClass", mapObjClass);
attrs.put(attr);
attr = new BasicAttribute(mapAttribute);
String[] map = mapping.getMapList();
for (int i = 0; i < map.length; i++)
{
attr.add(map[i]);
}
attrs.put(attr);
try
{
ctx.createSubcontext(mappingNode, attrs);
}
catch (Exception e)
{
attrs.remove("objectClass");
ctx.modifyAttributes(mappingNode, DirContext.REPLACE_ATTRIBUTE, attrs);
}
System.out.println("Total " + map.length + " mappings written");
}
catch (NamingException e)
{
throw new ConfigurationException("Unable to create LDAP node");
}
}
/**
* Command line driver.
*
* @param args
* Application command line parameters. File name is the only one
* expected.
*/
public static void main(String[] args)
{
try
{
LdapMapGen worker = new LdapMapGen(args);
worker.run();
}
catch (Exception e)
{
LOG.severe("", e);
}
}
}