DirectoryCopy.java
/*
** Module : DirectoryCopy.java
** Abstract : batch mode directory tree editor
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SIY 20050505 @21190 Created initial version
** 002 SIY 20090529 @42504 Accommodated to changes in DirectoryService API.
** 003 NVS 20090824 @43729 Extended the set of command line, offline directory
** utilities with these new utilities:
** - remove - recursively removes a branch
** - clear - deletes attributes conditionally or not
** - set - adds or sets attribute's value
** 004 NVS 20090826 @43772 Javadoc fix
** 005 NVS 20090915 @43900 Added another command line, offline directory
** utility selcopy for filtered copying of selected
** subnodes between directories.
** 006 NVS 20090924 @44027 Gradual attribute value creation used in set
** utility. The validity of the attribute us checked
** once the value is set.
** 007 GES 20101117 Switched to new static schema loader that reads
** from well-known resources rather than a manually
** defined configuration file.
** 008 EVL 20160225 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 009 IAS 20160331 Potential NPE fix
** 010 HC 29170612 Fixed several NPEs, removed references to AdminUtils.
** 011 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** 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 com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.util.logging.*;
import java.util.*;
/**
* This class implements a utility which provides batch mode directory editing.
* The utility does not depend on the type of the each back-end. It can be used
* as a backup utility or for making modifications.
* <p>
* Known limitations: <br>
* <ol>
* <li>Both back-ends must be completely configured and ready to use. In
* particular, if LDAP back-end uses <code>subtree</code> mapping mode then
* mapping data must be already present in LDAP tree.</li>
* <li>The utility performs copy, not backup, so it does not change nor
* remove existing nodes in the destination unless they are overwritten by
* existing nodes in the source. Therefore destination back-end tree must be
* empty if exact copy of the source is required.</li>
* </ol>
*/
class DirectoryCopy
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(DirectoryCopy.class);
/** Reference to command line parameters */
private String[] args;
/** utility statistics */
private int skipped = 0;
/** utility statistics */
private int found = 0;
/** utility statistics */
private int changed = 0;
/** utility statistics */
private int errors = 0;
/**
* Construct an instance of DirectoryCopy for given command line
* parameters.
*
* @param args
* Command line parameters.
*/
public DirectoryCopy(String[] args)
{
this.args = args;
}
/**
* Shortcut for the System.out.println()
*
* @param msg
* Message to print to standard output.
*/
static void say(String msg)
{
System.out.println(msg);
}
/**
* Shortcut for the System.out.print()
*
* @param msg
* Message to print to standard output.
*/
static void say_(String msg)
{
System.out.print(msg);
}
/**
* Protected Directory Copy utility.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void protCopy()
throws ConfigurationException
{
if (args.length != 4)
{
say("Usage: java DirectoryCopy protcopy <source-xml> <source-pw> "
+ "<target-xml> <target-pw>");
return;
}
SchemaLoad.initSchema();
String cfg1 = args[0];
String cfg2 = args[2];
char[] pass1 = args[1].toCharArray();
char[] pass2 = args[3].toCharArray();
BootstrapConfig c1 = new BootstrapConfig(cfg1, pass1, null, null);
BootstrapConfig c2 = new BootstrapConfig(cfg2, pass2, null, null);
Remapper src = DirectoryService.genRemapper(c1);
Remapper dst = DirectoryService.genRemapper(c2);
say("Starting copying...");
boolean res = true;
do
{
res = src.bind();
if (!res)
{
say("Bind to source failed");
break;
}
res = dst.bind();
if (!res)
{
say("Bind to destination failed");
break;
}
res = DirectoryService.copySubtree(src, "", dst, "", true);
if (res)
dst.update();
}
while (false);
boolean rc;
rc = src.unbind();
if (!rc)
say("Unbinding from source failed");
rc = dst.unbind();
if (!rc)
say("Unbinding from destination failed");
say(res ? "Done." : "Failed.");
}
/**
* Directory Copy utility.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void copy()
throws ConfigurationException
{
// check arguments
if (args.length != 4)
{
say("Usage: java DirectoryCopy copy <source-xml> <source-path> "
+ "<target-xml> <target-path>");
return;
}
// Empty BootstrapConfigs
BootstrapConfig bcs = new BootstrapConfig(null, null, null, null);
BootstrapConfig bct = new BootstrapConfig(null, null, null, null);
// directory package configuration
bcs.setServer(true);
bcs.setConfigItem("directory", "backend", "type", "xml");
bcs.setConfigItem("directory", "xml", "filename", args[0]);
bct.setServer(true);
bct.setConfigItem("directory", "backend", "type", "xml");
bct.setConfigItem("directory", "xml", "filename", args[2]);
// initialize directory schema
SchemaLoad.initSchema();
// instantiating directory tree backends
Remapper rs = DirectoryService.genRemapper(bcs);
Remapper rt = DirectoryService.genRemapper(bct);
boolean rc = true;
// binding to the directories
rc = rs.bind();
if (!rc)
{
say("Binding to source directory failed");
return;
}
rc = rt.bind();
if (!rc)
{
say("Binding to target directory failed");
rs.unbind();
return;
}
// copy the branch between directories
rc = DirectoryService.copySubtree(rs, args[1], rt, args[3], true);
if (rc)
{
rc = rt.update();
if (!rc)
{
say("committing failed");
}
}
else
{
say("copy operation failed");
}
// unbinding from the directories
rc = rs.unbind();
if (!rc)
{
say("Unbinding from source directory failed");
}
rc = rt.unbind();
if (!rc)
{
say("Unbinding from target directory failed");
}
}
/**
* Directory Branch Removal utility.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void remove()
throws ConfigurationException
{
// check arguments
if (args.length != 2)
{
say("Usage: java DirectoryCopy remove <target-xml> <target-path>");
return;
}
// Empty BootstrapConfig
BootstrapConfig bct = new BootstrapConfig(null, null, null, null);
// directory package configuration
bct.setServer(true);
bct.setConfigItem("directory", "backend", "type", "xml");
bct.setConfigItem("directory", "xml", "filename", args[0]);
// initialize directory schema
SchemaLoad.initSchema();
// instantiating directory tree backend
Remapper rt = DirectoryService.genRemapper(bct);
boolean rc = true;
// binding to the directory
rc = rt.bind();
if (!rc)
{
say("Binding to target directory failed");
return;
}
// check the branch for existence
if (rt.getNodeClassName(args[1]) == null)
{
say("no such node in the target directory");
rt.unbind();
return;
}
// remove the branch
rc = removeDirectoryNode(rt, args[1]);
if (rc)
{
rc = rt.update();
if (!rc)
{
say("committing failed");
}
}
else
{
say("remove operation failed");
}
// unbinding from the directories
rc = rt.unbind();
if (!rc)
{
say("Unbinding from target directory failed");
}
}
/**
* Attribute Value Clear utility. It scans the specified branch
* of the directory, selects all nodes of the given class and deletes
* the specified attribute entirely or just the specified value.
*
*/
private void clearNodeWorker(Remapper rt, String nodeId, String nclass,
String nattr, String val, boolean clearAll)
{
boolean rc = true;
String nodeClass = rt.getNodeClassName(nodeId);
if (!nodeClass.equalsIgnoreCase(nclass))
{
say("skipping " + nodeId + " " + nodeClass);
skipped++;
return;
}
say_("checking " + nodeId + " " + nodeClass);
found++;
// enumarate existing attributes
Attribute atv = null;
Attribute[] att = DirectoryService.getNodeAttributes(rt, nodeId);
if (att == null || att.length == 0)
{
say(", skipping");
skipped++;
return;
}
for (int j = 0; j < att.length; j++)
{
if (att[j].getName().equalsIgnoreCase(nattr))
{
atv = att[j];
break;
}
}
if (atv == null)
{
say(", skipping");
skipped++;
return;
}
if (clearAll)
{
// whole attribute deleted unconditionally
changed++;
rc = rt.deleteNodeAttribute(nodeId, nattr);
if (rc)
{
say(", cleared");
}
else
{
errors++;
say(", clearing failed");
}
}
else
{
// conditional deletion of a specific value
String[] values = atv.getValues();
List<String> valList = values == null ? Collections.emptyList() : Arrays.asList(values);
int iv = valList.indexOf(val);
if (iv == -1)
{
say(", skipping");
skipped++;
}
else
{
changed++;
rc = rt.deleteNodeAttributeValue(nodeId, nattr, iv);
if (rc)
{
say(String.format(", cleared at index %d", iv));
}
else
{
errors++;
say(String.format(", clearing at index %d failed", iv));
}
}
}
}
/**
* Attribute Value Clear utility. It scans the specified branch
* of the directory, selects all nodes of the given class and deletes
* the specified attribute entirely or just the specified value.
*/
private void clearWorker(Remapper rt, String node, String nclass,
String nattr, String val, boolean clearAll)
{
// process this node first
clearNodeWorker(rt, node, nclass, nattr, val, clearAll);
// enumerate immediate children
String[] nodes = rt.enumerateNodes(node);
if (nodes == null)
{
return;
}
// scan the children
for (int i = 0; i < nodes.length; i++)
{
clearWorker(rt, node + "/" + nodes[i], nclass, nattr, val, clearAll);
}
}
/**
* Attribute Value Clear utility. It scans the specified branch
* of the directory, selects all nodes of the given class and deletes
* the specified attribute entirely or just the specified value.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void clear(String[] args)
throws ConfigurationException
{
// check arguments
if (args.length != 4 && args.length != 5)
{
say("Usage: java DirectoryCopy clear <target-xml> <target-path> " +
"node-class attr-name [attr-value]");
return;
}
// true when the whole attribute is to be cleared
boolean clearAll = args.length == 4;
// Empty BootstrapConfig
BootstrapConfig bct = new BootstrapConfig(null, null, null, null);
// directory package configuration
bct.setServer(true);
bct.setConfigItem("directory", "backend", "type", "xml");
bct.setConfigItem("directory", "xml", "filename", args[0]);
// initialize directory schema
SchemaLoad.initSchema();
// instantiating directory tree backend
Remapper rt = DirectoryService.genRemapper(bct);
boolean rc = true;
// binding to the directory
rc = rt.bind();
if (!rc)
{
say("Binding to target directory failed");
return;
}
// check the branch for existence
if (rt.getNodeClassName(args[1]) == null)
{
say("no starting node in the target directory");
rt.unbind();
return;
}
// check the node class validity
String[] allClasses = rt.getClassNames();
List<String> classList = allClasses == null ? null : Arrays.asList(allClasses);
if (classList == null || !classList.contains(args[2]))
{
say("no such node class: " + args[2]);
rt.unbind();
return;
}
// check the attribute name validity
AttributeDefinition[] attrs = rt.getClassDefinition(args[2]);
if (attrs == null || attrs.length == 0)
{
say("class " + args[2] + " has no attributes");
rt.unbind();
return;
}
AttributeDefinition attr = null;
for (int i = 0; i < attrs.length; i++)
{
if (attrs[i].getName().equalsIgnoreCase(args[3]))
{
attr = attrs[i];
break;
}
}
if (attr == null)
{
say("class " + args[2] + " has no attribute " + args[3]);
say("valid attribute names are:");
for (int i = 0; i < attrs.length; i++)
{
say(" " + attrs[i].getName());
}
rt.unbind();
return;
}
// process the subtree
String[] nodes = rt.enumerateNodes(args[1]);
if (nodes == null)
{
say("no children at all");
return;
}
clearWorker(rt, args[1], args[2], args[3], clearAll ? null : args[4],
clearAll);
say(String.format("Nodes skipped %d, checked %d, changed %d, errors %d",
skipped, found, changed, errors));
if (found == 0)
{
say("no children of class " + args[2]);
rt.unbind();
return;
}
if (changed == 0)
{
say("no nodes were modified");
rt.unbind();
return;
}
// committing changes
if (rc)
{
rc = rt.update();
if (!rc)
{
say("committing failed");
}
else
{
say("success");
}
}
else
{
say("some clear operations failed");
}
// unbinding from the directories
rc = rt.unbind();
if (!rc)
{
say("Unbinding from target directory failed");
}
}
/**
* Attribute Value Set utility. It scans the specified branch
* of the directory, selects all nodes of the given class and adds or sets
* the specified attribute with specified value.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void set(String[] args)
throws ConfigurationException
{
// check arguments
if (args.length != 5 && args.length != 6)
{
say("Usage: java DirectoryCopy set <target-xml> <target-path> " +
"node-class attr-name attr-value [value-index]");
return;
}
int index = 0;
if (args.length == 6)
{
try
{
index = Integer.parseInt(args[5]);
}
catch (NumberFormatException nfx)
{
say("invalid index value");
return;
}
}
// Empty BootstrapConfig
BootstrapConfig bct = new BootstrapConfig(null, null, null, null);
// directory package configuration
bct.setServer(true);
bct.setConfigItem("directory", "backend", "type", "xml");
bct.setConfigItem("directory", "xml", "filename", args[0]);
// initialize directory schema
SchemaLoad.initSchema();
// instantiating directory tree backend
Remapper rt = DirectoryService.genRemapper(bct);
boolean rc = true;
// binding to the directory
rc = rt.bind();
if (!rc)
{
say("Binding to target directory failed");
return;
}
// check the branch for existence
if (rt.getNodeClassName(args[1]) == null)
{
say("no starting node in the target directory");
rt.unbind();
return;
}
// check the node class validity
String[] allClasses = rt.getClassNames();
List<String> classList = allClasses == null ? null : Arrays.asList(allClasses);
if (classList == null || !classList.contains(args[2]))
{
say("no such node class: " + args[2]);
rt.unbind();
return;
}
// check the attribute name validity
AttributeDefinition[] attrs = rt.getClassDefinition(args[2]);
if (attrs == null || attrs.length == 0)
{
say("class " + args[2] + " has no attributes");
rt.unbind();
return;
}
AttributeDefinition attr = null;
for (int i = 0; i < attrs.length; i++)
{
if (attrs[i].getName().equalsIgnoreCase(args[3]))
{
attr = attrs[i];
break;
}
}
if (attr == null)
{
say("class " + args[2] + " has no attribute " + args[3]);
say("valid attribute names are:");
for (int i = 0; i < attrs.length; i++)
{
say(" " + attrs[i].getName());
}
rt.unbind();
return;
}
// instantiate the new value of the attribute
Attribute data = new Attribute(attr.genNodeAttribute(), new Object[] {});
boolean valueValid = data.addStringValue(args[4]);
if (!valueValid || !data.isValid())
{
say("incompatible value for the attribute");
rt.unbind();
return;
}
Object attrValue = data.getValue(0);
// process the subtree
String[] nodes = rt.enumerateNodes(args[1]);
if (nodes == null)
{
say("no children at all");
rt.unbind();
return;
}
setWorker(rt, args[1], args[2], args[3], attr.getType(), attrValue,
index);
say(String.format("Nodes skipped %d, checked %d, changed %d, errors %d",
skipped, found, changed, errors));
if (found == 0)
{
say("no children of class " + args[2]);
rt.unbind();
return;
}
if (changed == 0)
{
say("no nodes were modified");
rt.unbind();
return;
}
// committing changes
if (rc)
{
rc = rt.update();
if (!rc)
{
say("committing failed");
}
else
{
say("success");
}
}
else
{
say("some clear operations failed");
}
// unbinding from the directories
rc = rt.unbind();
if (!rc)
{
say("Unbinding from target directory failed");
}
}
/**
* Selective directory copy. Takes two existing directories: the source and
* tge target, the path to scan and the filtering attribute name and value.
* <p>
* The filtering is done in the target directory. The path is enumerated and
* all the nodes of the given class are checked for the given subnodes'
* attribure value. If a node passes filtering, then the specified subnodes
* of the node in the source directory are copied to replace the existing
* subnodes in the target, if any.
* <p>
* Typical use would be: find all user account definitions in the target
* directory, having the user-num attribute 0, and copy from the source
* directory some account extensions like created-date, user-num, user-level,
* key.
*
* @throws ConfigurationException
* In case of configuration error.
*/
private void selectiveCopy(String[] args)
throws ConfigurationException
{
// check arguments
if (args.length != 8 && args.length != 9)
{
// 0 1
say("Usage: java DirectoryCopy selcopy <source-xml> <target-xml> " +
// 2 3 4 5 6
"<path>\n node-class subnode-class subnode-name attr-name " +
// 7 8
"attr-value [quoted-subnodes-to-copy]");
return;
}
// Empty BootstrapConfigs
BootstrapConfig bcs = new BootstrapConfig(null, null, null, null);
BootstrapConfig bct = new BootstrapConfig(null, null, null, null);
// directory package configuration
bcs.setServer(true);
bcs.setConfigItem("directory", "backend", "type", "xml");
bcs.setConfigItem("directory", "xml", "filename", args[0]);
bct.setServer(true);
bct.setConfigItem("directory", "backend", "type", "xml");
bct.setConfigItem("directory", "xml", "filename", args[1]);
// initialize directory schema
SchemaLoad.initSchema();
// instantiating directory tree backends
Remapper rs = DirectoryService.genRemapper(bcs);
Remapper rt = DirectoryService.genRemapper(bct);
boolean rc = true;
// binding to the directories
rc = rs.bind();
if (!rc)
{
say("Binding to source directory failed");
return;
}
rc = rt.bind();
if (!rc)
{
say("Binding to target directory failed");
rs.unbind();
return;
}
// check the branch for existence
if (rs.getNodeClassName(args[2]) == null)
{
say("no starting node in the source directory");
rs.unbind();
rt.unbind();
return;
}
if (rt.getNodeClassName(args[2]) == null)
{
say("no starting node in the target directory");
rs.unbind();
rt.unbind();
return;
}
// check the node, subnode class validity
String[] allClasses = rs.getClassNames();
List<String> classList = allClasses == null ? null : Arrays.asList(allClasses);
if (classList == null || !classList.contains(args[3]))
{
say("no such node class: " + args[3]);
rs.unbind();
rt.unbind();
return;
}
if (!classList.contains(args[4]))
{
say("no such node class: " + args[4]);
rs.unbind();
rt.unbind();
return;
}
allClasses = rt.getClassNames();
classList = allClasses == null ? null : Arrays.asList(allClasses);
if (classList == null || !classList.contains(args[3]))
{
say("no such node class: " + args[3]);
rs.unbind();
rt.unbind();
return;
}
if (!classList.contains(args[4]))
{
say("no such node class: " + args[4]);
rs.unbind();
rt.unbind();
return;
}
// check the attribute name validity
AttributeDefinition[] attrs = rs.getClassDefinition(args[4]);
if (attrs == null || attrs.length == 0)
{
say("class " + args[4] + " has no attributes");
rs.unbind();
rt.unbind();
return;
}
AttributeDefinition attr = null;
for (int i = 0; i < attrs.length; i++)
{
if (attrs[i].getName().equalsIgnoreCase(args[6]))
{
attr = attrs[i];
break;
}
}
if (attr == null)
{
say("class " + args[4] + " has no attribute " + args[6]);
say("valid attribute names are:");
for (int i = 0; i < attrs.length; i++)
{
say(" " + attrs[i].getName());
}
rs.unbind();
rt.unbind();
return;
}
attrs = rt.getClassDefinition(args[4]);
if (attrs == null || attrs.length == 0)
{
say("class " + args[4] + " has no attributes");
rs.unbind();
rt.unbind();
return;
}
for (int i = 0; i < attrs.length; i++)
{
if (attrs[i].getName().equalsIgnoreCase(args[6]))
{
attr = attrs[i];
break;
}
}
if (attr == null)
{
say("class " + args[4] + " has no attribute " + args[6]);
say("valid attribute names are:");
for (int i = 0; i < attrs.length; i++)
{
say(" " + attrs[i].getName());
}
rs.unbind();
rt.unbind();
return;
}
// process copy-list
String[] cpys = null;
if (args.length == 9 && args[8].length() > 0)
{
cpys = args[8].trim().split("(\\p{Space})+");
if (cpys.length == 0)
{
cpys = null;
}
}
// selectively copy nodes between directories
rc = selectiveCopyWorker(rs, rt, args[2], args[3], args[4], args[5],
args[6], args[7], cpys);
say(String.format("Nodes skipped %d, checked %d, changed %d, errors %d",
skipped, found, changed, errors));
if (found == 0)
{
say("no nodes of the class " + args[3] + " found");
rs.unbind();
rt.unbind();
return;
}
if (changed == 0)
{
say("no matching values found");
rs.unbind();
rt.unbind();
return;
}
if (changed > errors)
{
// not all changes failed; there is something to save
rc = rt.update();
if (!rc)
{
say("committing failed");
}
else
{
say("success");
}
}
if (errors > 0)
{
say("some copy operations failed");
}
// unbinding from the directories
rc = rs.unbind();
if (!rc)
{
say("Unbinding from source directory failed");
}
rc = rt.unbind();
if (!rc)
{
say("Unbinding from target directory failed");
}
}
/**
* Selective copy worker.
*
* @return <code>true</code> if success
*/
private boolean selectiveCopyWorker(Remapper rs, Remapper rt, String path,
String nclass, String sclass,
String subnode, String attrName,
String val, String[] cpys)
{
// enumerate the nodes under the given path in target
String[] nodes = rt.enumerateNodes(path);
if (nodes == null)
{
say("no children at all");
return true;
}
// filter the nodes by class
for (int i = 0; i < nodes.length; i++)
{
String nodeId = path + "/" + nodes[i];
String nodeClass = rt.getNodeClassName(nodeId);
if (!nodeClass.equalsIgnoreCase(nclass))
{
say("skipping " + nodeId + " " + nodeClass);
skipped++;
continue;
}
say_("checking " + nodeId + " " + nodeClass);
found++;
// check if there is named subnode of the given class
String subNodeId = nodeId + "/" + subnode;
String subNodeClass = rt.getNodeClassName(subNodeId);
if (subNodeClass == null || !subNodeClass.equalsIgnoreCase(sclass))
{
say(", skipping (no subnode)");
skipped++;
continue;
}
// enumarate existing attributes
Attribute atv = null;
Attribute[] att = DirectoryService.getNodeAttributes(rt, subNodeId);
if (att == null || att.length == 0)
{
say(", skipping (no subnode attributes)");
skipped++;
continue;
}
for (int j = 0; j < att.length; j++)
{
if (att[j].getName().equalsIgnoreCase(attrName))
{
atv = att[j];
break;
}
}
if (atv == null)
{
say(", skipping (no " + attrName + " attribute");
skipped++;
continue;
}
// filtering by a specific value
String value = atv.getStringValue(0);
if (value == null)
{
say(", skipping (no values)");
skipped++;
continue;
}
if (!val.equalsIgnoreCase(value))
{
say(", skipping (value mismatch)");
skipped++;
continue;
}
// found a match; copy listed subnodes from the source
say(", copying");
if (cpys == null)
{
continue;
}
for (int j = 0; j < cpys.length; j++)
{
say_(" " + cpys[j]);
changed++;
boolean rc = DirectoryService.copySubtree(
rs, nodeId + "/" + cpys[j],
rt, nodeId + "/" + cpys[j], true);
if (rc)
{
say(", OK");
}
else
{
say(", failed");
errors++;
}
}
}
return errors == 0;
}
/**
* Attribute Value Set worker. It scans the specified branch
* of the directory, selects all nodes of the given class and deletes
* the specified attribute entirely or just the specified value.
*/
private void setWorker(Remapper rt, String node, String nclass,
String nattr, int attrType, Object val, int index)
{
// process this node first
setNodeWorker(rt, node, nclass, nattr, attrType, val, index);
// enumerate immediate children
String[] nodes = rt.enumerateNodes(node);
if (nodes == null)
{
return;
}
// scan the children
for (int i = 0; i < nodes.length; i++)
{
setWorker(rt, node + "/" + nodes[i], nclass, nattr, attrType, val,
index);
}
}
/**
* Attribute Value Set node worker. It scans the specified branch
* of the directory, selects all nodes of the given class and deletes
* the specified attribute entirely or just the specified value.
*
*/
private void setNodeWorker(Remapper rt, String nodeId, String nclass,
String nattr, int attrType, Object val,
int index)
{
boolean rc = true;
String nodeClass = rt.getNodeClassName(nodeId);
if (!nodeClass.equalsIgnoreCase(nclass))
{
say("skipping " + nodeId + " " + nodeClass);
skipped++;
return;
}
say_("checking " + nodeId + " " + nodeClass);
found++;
// enumerate existing attributes
Attribute atv = null;
Attribute[] att = DirectoryService.getNodeAttributes(rt, nodeId);
if (att != null && att.length > 0)
{
for (int j = 0; j < att.length; j++)
{
if (att[j].getName().equalsIgnoreCase(nattr))
{
atv = att[j];
break;
}
}
}
// conditional setting of a specific value
String[] values = new String[] {};
if (atv != null)
{
values = atv.getValues();
}
if (index > values.length)
{
say(String.format(", skipping - only %d values", values.length));
skipped++;
}
else
{
changed++;
boolean add = index == values.length;
switch(attrType)
{
case AttributeType.ATTR_INTEGER:
int ival = ((Integer)val).intValue();
if (add)
{
rc = rt.addNodeInteger(nodeId, nattr, ival);
}
else
{
rc = rt.setNodeInteger(nodeId, nattr, index, ival);
}
break;
case AttributeType.ATTR_BOOLEAN:
boolean bval = ((Boolean)val).booleanValue();
if (add)
{
rc = rt.addNodeBoolean(nodeId, nattr, bval);
}
else
{
rc = rt.setNodeBoolean(nodeId, nattr, index, bval);
}
break;
case AttributeType.ATTR_STRING:
if (add)
{
rc = rt.addNodeString(nodeId, nattr, (String)val);
}
else
{
rc = rt.setNodeString(nodeId, nattr, index, (String)val);
}
break;
case AttributeType.ATTR_DOUBLE:
double dval = ((Double)val).doubleValue();
if (add)
{
rc = rt.addNodeDouble(nodeId, nattr, dval);
}
else
{
rc = rt.setNodeDouble(nodeId, nattr, index, dval);
}
break;
case AttributeType.ATTR_BYTEARRAY:
if (add)
{
rc = rt.addNodeByteArray(nodeId, nattr, (byte[])val);
}
else
{
rc = rt.setNodeByteArray(nodeId, nattr, index, (byte[])val);
}
break;
case AttributeType.ATTR_BITFIELD:
if (add)
{
rc = rt.addNodeBitField(nodeId, nattr, (BitField)val);
}
else
{
rc = rt.setNodeBitField(nodeId, nattr, index, (BitField)val);
}
break;
case AttributeType.ATTR_BITSELECTOR:
if (add)
{
rc = rt.addNodeBitSelector(nodeId, nattr, (BitSelector)val);
}
else
{
rc = rt.setNodeBitSelector(nodeId, nattr, index,
(BitSelector)val);
}
break;
case AttributeType.ATTR_DATE:
if (add)
{
rc = rt.addNodeDate(nodeId, nattr, (DateValue)val);
}
else
{
rc = rt.setNodeDate(nodeId, nattr, index, (DateValue)val);
}
break;
case AttributeType.ATTR_TIME:
if (add)
{
rc = rt.addNodeTime(nodeId, nattr, (TimeValue)val);
}
else
{
rc = rt.setNodeTime(nodeId, nattr, index, (TimeValue)val);
}
break;
}
if (rc)
{
say(", set");
}
else
{
errors++;
say(", setting failed");
}
}
}
/**
* Removes the specified directory node, including all child nodes.
*
* @param rm
* A bound directory remapper
*
* @param nodeId
* ID of node to remove
*
* @return <code>true</code> if removed the node successfully
*/
private static boolean removeDirectoryNode(Remapper rm, String nodeId)
{
// enumerate and remove all children first, recursively
String[] child = rm.enumerateNodes(nodeId);
if (child == null)
{
return false;
}
for (int i = 0; i < child.length; i++)
{
if (!removeDirectoryNode(rm, nodeId + "/" + child[i]))
{
return false;
}
}
// remove the specified node now
return rm.deleteNode(nodeId);
}
/**
* Application entry point. Since more than one operation cab be performed,
* the operation has to be specified with the first argument.
*
* @param args
* Command line parameters. The first one specifies the utility
* and the rest is utility specific. The currently implemented
* utilities are:
* <ul>
* <li>protcopy - protected copy which copies the contents of
* the source encrypted directory to the target
* encrypted directory
* <li>copy - works with unencrypted directories; copies a
* branch of the source directory to some insertion
* point in the target directory
* <li>remove - works with unencrypted directories; removes a
* branch of the target directory
* <li>clear - works with unencrypted directories; removes an
* attribute or one of its values
* <li>set - works with unencrypted directories; sets the given
* attribute to the given value
* <li>selcopy - works with unencrypted directories; selectively
* copies some subnodes from the source directory
* to the target directory
* </ul>
*/
public static void main(String[] args)
{
// inspect the first argument and identify the operation
int argn = args.length;
int oper = 0;
boolean fail = false;
if (argn == 0)
{
fail = true;
}
else
{
if (args[0].equalsIgnoreCase("protcopy"))
{
oper = 1;
}
else if (args[0].equalsIgnoreCase("copy"))
{
oper = 2;
}
else if (args[0].equalsIgnoreCase("remove"))
{
oper = 3;
}
else if (args[0].equalsIgnoreCase("clear"))
{
oper = 4;
}
else if (args[0].equalsIgnoreCase("set"))
{
oper = 5;
}
else if (args[0].equalsIgnoreCase("selcopy"))
{
oper = 6;
}
else
{
fail = true;
}
}
if (fail)
{
say("A valid operation should be specified");
say("Valid operations are:");
say(" protcopy");
say(" copy");
say(" remove");
say(" clear");
say(" set");
say(" selcopy");
say("Type <operation ?> for more help");
System.exit(1);
}
// prepare the parameters for the operation
argn--;
String[] pars = new String[argn];
System.arraycopy(args, 1, pars, 0, argn);
// instantiate the class
DirectoryCopy dir = new DirectoryCopy(pars);
// invoke the utility
try
{
switch(oper)
{
case 1:
dir.protCopy();
break;
case 2:
dir.copy();
break;
case 3:
dir.remove();
break;
case 4:
dir.clear(pars);
break;
case 5:
dir.set(pars);
break;
case 6:
dir.selectiveCopy(pars);
break;
}
}
catch (ConfigurationException e)
{
LOG.severe("", e);
}
}
}