/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2013 ForgeRock AS.
*/
package org.opends.server.tools.makeldif;
import org.opends.messages.Message;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines a template file, which is a collection of constant
* definitions, branches, and templates.
*/
public class TemplateFile
{
/**
* The name of the file holding the list of first names.
*/
public static final String FIRST_NAME_FILE = "first.names";
/**
* The name of the file holding the list of last names.
*/
public static final String LAST_NAME_FILE = "last.names";
// A map of the contents of various text files used during the parsing
// process, mapped from absolute path to the array of lines in the file.
private HashMap<String,String[]> fileLines;
// The index of the next first name value that should be used.
private int firstNameIndex;
// The index of the next last name value that should be used.
private int lastNameIndex;
// A counter used to keep track of the number of times that the larger of the
// first/last name list has been completed.
private int nameLoopCounter;
// A counter that will be used in case we have exhausted all possible first
// and last name combinations.
private int nameUniquenessCounter;
// The set of branch definitions for this template file.
private LinkedHashMap<DN,Branch> branches;
// The set of constant definitions for this template file.
private LinkedHashMap<String,String> constants;
// The set of registered tags for this template file.
private LinkedHashMap<String,Tag> registeredTags;
// The set of template definitions for this template file.
private LinkedHashMap<String,Template> templates;
// The random number generator for this template file.
private Random random;
// The next first name that should be used.
private String firstName;
// The next last name that should be used.
private String lastName;
// The resource path to use for filesystem elements that cannot be found
// anywhere else.
private String resourcePath;
// The path to the directory containing the template file, if available.
private String templatePath;
// The set of first names to use when generating the LDIF.
private String[] firstNames;
// The set of last names to use when generating the LDIF.
private String[] lastNames;
/**
* Creates a new, empty template file structure.
*
* @param resourcePath The path to the directory that may contain additional
* resource files needed during the LDIF generation
* process.
*/
public TemplateFile(String resourcePath)
{
this(resourcePath, new Random());
}
/**
* Creates a new, empty template file structure.
*
*
* @param resourcePath The path to the directory that may contain additional
* resource files needed during the LDIF generation
* process.
* @param random The random number generator for this template file.
*/
public TemplateFile(String resourcePath, Random random)
{
this.resourcePath = resourcePath;
this.random = random;
fileLines = new HashMap<String,String[]>();
branches = new LinkedHashMap<DN,Branch>();
constants = new LinkedHashMap<String,String>();
registeredTags = new LinkedHashMap<String,Tag>();
templates = new LinkedHashMap<String,Template>();
templatePath = null;
firstNames = new String[0];
lastNames = new String[0];
firstName = null;
lastName = null;
firstNameIndex = 0;
lastNameIndex = 0;
nameLoopCounter = 0;
nameUniquenessCounter = 1;
registerDefaultTags();
try
{
readNameFiles();
}
catch (IOException ioe)
{
// FIXME -- What to do here?
ioe.printStackTrace();
firstNames = new String[] { "John" };
lastNames = new String[] { "Doe" };
}
}
/**
* Retrieves the set of tags that have been registered. They will be in the
* form of a mapping between the name of the tag (in all lowercase characters)
* and the corresponding tag implementation.
*
* @return The set of tags that have been registered.
*/
public Map<String,Tag> getTags()
{
return registeredTags;
}
/**
* Retrieves the tag with the specified name.
*
* @param lowerName The name of the tag to retrieve, in all lowercase
* characters.
*
* @return The requested tag, or <CODE>null</CODE> if no such tag has been
* registered.
*/
public Tag getTag(String lowerName)
{
return registeredTags.get(lowerName);
}
/**
* Registers the specified class as a tag that may be used in templates.
*
* @param tagClass The fully-qualified name of the class to register as a
* tag.
*
* @throws MakeLDIFException If a problem occurs while attempting to
* register the specified tag.
*/
public void registerTag(String tagClass)
throws MakeLDIFException
{
Class c;
try
{
c = Class.forName(tagClass);
}
catch (Exception e)
{
Message message = ERR_MAKELDIF_CANNOT_LOAD_TAG_CLASS.get(tagClass);
throw new MakeLDIFException(message, e);
}
Tag t;
try
{
t = (Tag) c.newInstance();
}
catch (Exception e)
{
Message message = ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(tagClass);
throw new MakeLDIFException(message, e);
}
String lowerName = toLowerCase(t.getName());
if (registeredTags.containsKey(lowerName))
{
Message message =
ERR_MAKELDIF_CONFLICTING_TAG_NAME.get(tagClass, t.getName());
throw new MakeLDIFException(message);
}
else
{
registeredTags.put(lowerName, t);
}
}
/**
* Registers the set of tags that will always be available for use in
* templates.
*/
private void registerDefaultTags()
{
Class[] defaultTagClasses = new Class[]
{
AttributeValueTag.class,
DNTag.class,
FileTag.class,
FirstNameTag.class,
GUIDTag.class,
IfAbsentTag.class,
IfPresentTag.class,
LastNameTag.class,
ListTag.class,
ParentDNTag.class,
PresenceTag.class,
RandomTag.class,
RDNTag.class,
SequentialTag.class,
StaticTextTag.class,
UnderscoreDNTag.class,
UnderscoreParentDNTag.class
};
for (Class c : defaultTagClasses)
{
try
{
Tag t = (Tag) c.newInstance();
registeredTags.put(toLowerCase(t.getName()), t);
}
catch (Exception e)
{
// This should never happen.
e.printStackTrace();
}
}
}
/**
* Retrieves the set of constants defined for this template file.
*
* @return The set of constants defined for this template file.
*/
public Map<String,String> getConstants()
{
return constants;
}
/**
* Retrieves the value of the constant with the specified name.
*
* @param lowerName The name of the constant to retrieve, in all lowercase
* characters.
*
* @return The value of the constant with the specified name, or
* <CODE>null</CODE> if there is no such constant.
*/
public String getConstant(String lowerName)
{
return constants.get(lowerName);
}
/**
* Registers the provided constant for use in the template.
*
* @param name The name for the constant.
* @param value The value for the constant.
*/
public void registerConstant(String name, String value)
{
constants.put(toLowerCase(name), value);
}
/**
* Retrieves the set of branches defined in this template file.
*
* @return The set of branches defined in this template file.
*/
public Map<DN,Branch> getBranches()
{
return branches;
}
/**
* Retrieves the branch registered with the specified DN.
*
* @param branchDN The DN for which to retrieve the corresponding branch.
*
* @return The requested branch, or <CODE>null</CODE> if no such branch has
* been registered.
*/
public Branch getBranch(DN branchDN)
{
return branches.get(branchDN);
}
/**
* Registers the provided branch in this template file.
*
* @param branch The branch to be registered.
*/
public void registerBranch(Branch branch)
{
branches.put(branch.getBranchDN(), branch);
}
/**
* Retrieves the set of templates defined in this template file.
*
* @return The set of templates defined in this template file.
*/
public Map<String,Template> getTemplates()
{
return templates;
}
/**
* Retrieves the template with the specified name.
*
* @param lowerName The name of the template to retrieve, in all lowercase
* characters.
*
* @return The requested template, or <CODE>null</CODE> if there is no such
* template.
*/
public Template getTemplate(String lowerName)
{
return templates.get(lowerName);
}
/**
* Registers the provided template for use in this template file.
*
* @param template The template to be registered.
*/
public void registerTemplate(Template template)
{
templates.put(toLowerCase(template.getName()), template);
}
/**
* Retrieves the random number generator for this template file.
*
* @return The random number generator for this template file.
*/
public Random getRandom()
{
return random;
}
/**
* Reads the contents of the first and last name files into the appropriate
* arrays and sets up the associated index pointers.
*
* @throws IOException If a problem occurs while reading either of the
* files.
*/
private void readNameFiles()
throws IOException
{
File f = getFile(FIRST_NAME_FILE);
ArrayList<String> nameList = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new FileReader(f));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
else
{
nameList.add(line);
}
}
reader.close();
firstNames = new String[nameList.size()];
nameList.toArray(firstNames);
f = getFile(LAST_NAME_FILE);
nameList = new ArrayList<String>();
reader = new BufferedReader(new FileReader(f));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
else
{
nameList.add(line);
}
}
reader.close();
lastNames = new String[nameList.size()];
nameList.toArray(lastNames);
}
/**
* Updates the first and last name indexes to choose new values. The
* algorithm used is designed to ensure that the combination of first and last
* names will never be repeated. It depends on the number of first names and
* the number of last names being relatively prime. This method should be
* called before beginning generation of each template entry.
*/
public void nextFirstAndLastNames()
{
firstName = firstNames[firstNameIndex++];
lastName = lastNames[lastNameIndex++];
// If we've already exhausted every possible combination, then append an
// integer to the last name.
if (nameUniquenessCounter > 1)
{
lastName += nameUniquenessCounter;
}
if (firstNameIndex >= firstNames.length)
{
// We're at the end of the first name list, so start over. If the first
// name list is larger than the last name list, then we'll also need to
// set the last name index to the next loop counter position.
firstNameIndex = 0;
if (firstNames.length > lastNames.length)
{
lastNameIndex = ++nameLoopCounter;
if (lastNameIndex >= lastNames.length)
{
lastNameIndex = 0;
nameUniquenessCounter++;
}
}
}
if (lastNameIndex >= lastNames.length)
{
// We're at the end of the last name list, so start over. If the last
// name list is larger than the first name list, then we'll also need to
// set the first name index to the next loop counter position.
lastNameIndex = 0;
if (lastNames.length > firstNames.length)
{
firstNameIndex = ++nameLoopCounter;
if (firstNameIndex >= firstNames.length)
{
firstNameIndex = 0;
nameUniquenessCounter++;
}
}
}
}
/**
* Retrieves the first name value that should be used for the current entry.
*
* @return The first name value that should be used for the current entry.
*/
public String getFirstName()
{
return firstName;
}
/**
* Retrieves the last name value that should be used for the current entry.
*
* @return The last name value that should be used for the current entry.
*/
public String getLastName()
{
return lastName;
}
/**
* Parses the contents of the specified file as a MakeLDIF template file
* definition.
*
* @param filename The name of the file containing the template data.
* @param warnings A list into which any warnings identified may be placed.
*
* @throws IOException If a problem occurs while attempting to read data
* from the specified file.
*
* @throws InitializationException If a problem occurs while initializing
* any of the MakeLDIF components.
*
* @throws MakeLDIFException If any other problem occurs while parsing the
* template file.
*/
public void parse(String filename, List<Message> warnings)
throws IOException, InitializationException, MakeLDIFException
{
ArrayList<String> fileLines = new ArrayList<String>();
templatePath = null;
File f = getFile(filename);
if ((f == null) || (! f.exists()))
{
Message message = ERR_MAKELDIF_COULD_NOT_FIND_TEMPLATE_FILE.get(filename);
throw new IOException(message.toString());
}
else
{
templatePath = f.getParentFile().getAbsolutePath();
}
BufferedReader reader = new BufferedReader(new FileReader(f));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
else
{
fileLines.add(line);
}
}
reader.close();
String[] lines = new String[fileLines.size()];
fileLines.toArray(lines);
parse(lines, warnings);
}
/**
* Parses the data read from the provided input stream as a MakeLDIF template
* file definition.
*
* @param inputStream The input stream from which to read the template file
* data.
* @param warnings A list into which any warnings identified may be
* placed.
*
* @throws IOException If a problem occurs while attempting to read data
* from the provided input stream.
*
* @throws InitializationException If a problem occurs while initializing
* any of the MakeLDIF components.
*
* @throws MakeLDIFException If any other problem occurs while parsing the
* template file.
*/
public void parse(InputStream inputStream, List<Message> warnings)
throws IOException, InitializationException, MakeLDIFException
{
ArrayList<String> fileLines = new ArrayList<String>();
BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
else
{
fileLines.add(line);
}
}
reader.close();
String[] lines = new String[fileLines.size()];
fileLines.toArray(lines);
parse(lines, warnings);
}
/**
* Parses the provided data as a MakeLDIF template file definition.
*
* @param lines The lines that make up the template file.
* @param warnings A list into which any warnings identified may be placed.
*
* @throws InitializationException If a problem occurs while initializing
* any of the MakeLDIF components.
*
* @throws MakeLDIFException If any other problem occurs while parsing the
* template file.
*/
public void parse(String[] lines, List<Message> warnings)
throws InitializationException, MakeLDIFException
{
// Create temporary variables that will be used to hold the data read.
LinkedHashMap<String,Tag> templateFileIncludeTags =
new LinkedHashMap<String,Tag>();
LinkedHashMap<String,String> templateFileConstants =
new LinkedHashMap<String,String>();
LinkedHashMap<DN,Branch> templateFileBranches =
new LinkedHashMap<DN,Branch>();
LinkedHashMap<String,Template> templateFileTemplates =
new LinkedHashMap<String,Template>();
for (int lineNumber=0; lineNumber < lines.length; lineNumber++)
{
String line = lines[lineNumber];
line = replaceConstants(line, lineNumber,
templateFileConstants, warnings);
String lowerLine = toLowerCase(line);
if ((line.length() == 0) || line.startsWith("#"))
{
// This is a comment or a blank line, so we'll ignore it.
continue;
}
else if (lowerLine.startsWith("include "))
{
// This should be an include definition. The next element should be the
// name of the class. Load and instantiate it and make sure there are
// no conflicts.
String className = line.substring(8).trim();
Class tagClass;
try
{
tagClass = Class.forName(className);
}
catch (Exception e)
{
Message message = ERR_MAKELDIF_CANNOT_LOAD_TAG_CLASS.get(className);
throw new MakeLDIFException(message, e);
}
Tag tag;
try
{
tag = (Tag) tagClass.newInstance();
}
catch (Exception e)
{
Message message = ERR_MAKELDIF_CANNOT_INSTANTIATE_TAG.get(className);
throw new MakeLDIFException(message, e);
}
String lowerName = toLowerCase(tag.getName());
if (registeredTags.containsKey(lowerName) ||
templateFileIncludeTags.containsKey(lowerName))
{
Message message =
ERR_MAKELDIF_CONFLICTING_TAG_NAME.get(className, tag.getName());
throw new MakeLDIFException(message);
}
templateFileIncludeTags.put(lowerName, tag);
}
else if (lowerLine.startsWith("define "))
{
// This should be a constant definition. The rest of the line should
// contain the constant name, an equal sign, and the constant value.
int equalPos = line.indexOf('=', 7);
if (equalPos < 0)
{
Message message = ERR_MAKELDIF_DEFINE_MISSING_EQUALS.get(lineNumber);
throw new MakeLDIFException(message);
}
String name = line.substring(7, equalPos).trim();
if (name.length() == 0)
{
Message message = ERR_MAKELDIF_DEFINE_NAME_EMPTY.get(lineNumber);
throw new MakeLDIFException(message);
}
String lowerName = toLowerCase(name);
if (templateFileConstants.containsKey(lowerName))
{
Message message =
ERR_MAKELDIF_CONFLICTING_CONSTANT_NAME.get(name, lineNumber);
throw new MakeLDIFException(message);
}
String value = line.substring(equalPos+1);
if (value.length() == 0)
{
Message message = ERR_MAKELDIF_WARNING_DEFINE_VALUE_EMPTY.get(
name, lineNumber);
warnings.add(message);
}
templateFileConstants.put(lowerName, value);
}
else if (lowerLine.startsWith("branch: "))
{
int startLineNumber = lineNumber;
ArrayList<String> lineList = new ArrayList<String>();
lineList.add(line);
while (true)
{
lineNumber++;
if (lineNumber >= lines.length)
{
break;
}
line = lines[lineNumber];
if (line.length() == 0)
{
break;
}
else
{
line = replaceConstants(line, lineNumber,
templateFileConstants, warnings);
lineList.add(line);
}
}
String[] branchLines = new String[lineList.size()];
lineList.toArray(branchLines);
Branch b = parseBranchDefinition(branchLines, lineNumber,
templateFileIncludeTags,
warnings);
DN branchDN = b.getBranchDN();
if (templateFileBranches.containsKey(branchDN))
{
Message message = ERR_MAKELDIF_CONFLICTING_BRANCH_DN.get(
String.valueOf(branchDN), startLineNumber);
throw new MakeLDIFException(message);
}
else
{
templateFileBranches.put(branchDN, b);
}
}
else if (lowerLine.startsWith("template: "))
{
int startLineNumber = lineNumber;
ArrayList<String> lineList = new ArrayList<String>();
lineList.add(line);
while (true)
{
lineNumber++;
if (lineNumber >= lines.length)
{
break;
}
line = lines[lineNumber];
if (line.length() == 0)
{
break;
}
else
{
line = replaceConstants(line, lineNumber,
templateFileConstants, warnings);
lineList.add(line);
}
}
String[] templateLines = new String[lineList.size()];
lineList.toArray(templateLines);
Template t = parseTemplateDefinition(templateLines, startLineNumber,
templateFileIncludeTags,
templateFileTemplates, warnings);
String lowerName = toLowerCase(t.getName());
if (templateFileTemplates.containsKey(lowerName))
{
Message message = ERR_MAKELDIF_CONFLICTING_TEMPLATE_NAME.get(
String.valueOf(t.getName()), startLineNumber);
throw new MakeLDIFException(message);
}
else
{
templateFileTemplates.put(lowerName, t);
}
}
else
{
Message message =
ERR_MAKELDIF_UNEXPECTED_TEMPLATE_FILE_LINE.get(line, lineNumber);
throw new MakeLDIFException(message);
}
}
// If we've gotten here, then we're almost done. We just need to finalize
// the branch and template definitions and then update the template file
// variables.
for (Branch b : templateFileBranches.values())
{
b.completeBranchInitialization(templateFileTemplates);
}
for (Template t : templateFileTemplates.values())
{
t.completeTemplateInitialization(templateFileTemplates);
}
registeredTags.putAll(templateFileIncludeTags);
constants.putAll(templateFileConstants);
branches.putAll(templateFileBranches);
templates.putAll(templateFileTemplates);
}
/**
* Parse a line and replace all constants within [ ] with their
* values.
*
* @param line The line to parse.
* @param lineNumber The line number in the template file.
* @param constants The set of constants defined in the template file.
* @param warnings A list into which any warnings identified may be
* placed.
* @return The line in which all constant variables have been replaced
* with their value
*/
private String replaceConstants(String line, int lineNumber,
Map<String,String> constants,
List<Message> warnings)
{
int closePos = line.lastIndexOf(']');
// Loop until we've scanned all closing brackets
do
{
// Skip escaped closing brackets
while (closePos > 0 &&
line.charAt(closePos - 1) == '\\')
{
closePos = line.lastIndexOf(']', closePos - 1);
}
if (closePos > 0)
{
StringBuilder lineBuffer = new StringBuilder(line);
int openPos = line.lastIndexOf('[', closePos);
// Find the opening bracket. If it's escaped, then it's not a constant
if ((openPos > 0 && line.charAt(openPos - 1) != '\\') ||
(openPos == 0))
{
String constantName =
toLowerCase(line.substring(openPos+1, closePos));
String constantValue = constants.get(constantName);
if (constantValue == null)
{
Message message = WARN_MAKELDIF_WARNING_UNDEFINED_CONSTANT.get(
constantName, lineNumber);
warnings.add(message);
}
else
{
lineBuffer.replace(openPos, closePos+1, constantValue);
}
}
if (openPos >= 0)
{
closePos = openPos;
}
line = lineBuffer.toString();
closePos = line.lastIndexOf(']', closePos);
}
} while (closePos > 0);
return line;
}
/**
* Parses the information contained in the provided set of lines as a MakeLDIF
* branch definition.
*
*
* @param branchLines The set of lines containing the branch definition.
* @param startLineNumber The line number in the template file on which the
* first of the branch lines appears.
* @param tags The set of defined tags from the template file.
* Note that this does not include the tags that are
* always registered by default.
* @param warnings A list into which any warnings identified may be
* placed.
*
* @return The decoded branch definition.
*
* @throws InitializationException If a problem occurs while initializing
* any of the branch elements.
*
* @throws MakeLDIFException If some other problem occurs during processing.
*/
private Branch parseBranchDefinition(String[] branchLines,
int startLineNumber,
Map<String, Tag> tags,
List<Message> warnings)
throws InitializationException, MakeLDIFException
{
// The first line must be "branch: " followed by the branch DN.
String dnString = branchLines[0].substring(8).trim();
DN branchDN;
try
{
branchDN = DN.decode(dnString);
}
catch (Exception e)
{
Message message =
ERR_MAKELDIF_CANNOT_DECODE_BRANCH_DN.get(dnString, startLineNumber);
throw new MakeLDIFException(message);
}
// Create a new branch that will be used for the verification process.
Branch branch = new Branch(this, branchDN);
for (int i=1; i < branchLines.length; i++)
{
String line = branchLines[i];
String lowerLine = toLowerCase(line);
int lineNumber = startLineNumber + i;
if (lowerLine.startsWith("#"))
{
// It's a comment, so we should ignore it.
continue;
}
else if (lowerLine.startsWith("subordinatetemplate: "))
{
// It's a subordinate template, so we'll want to parse the name and the
// number of entries.
int colonPos = line.indexOf(':', 21);
if (colonPos <= 21)
{
Message message = ERR_MAKELDIF_BRANCH_SUBORDINATE_TEMPLATE_NO_COLON.
get(lineNumber, dnString);
throw new MakeLDIFException(message);
}
String templateName = line.substring(21, colonPos).trim();
int numEntries;
try
{
numEntries = Integer.parseInt(line.substring(colonPos+1).trim());
if (numEntries < 0)
{
Message message =
ERR_MAKELDIF_BRANCH_SUBORDINATE_INVALID_NUM_ENTRIES.
get(lineNumber, dnString, numEntries, templateName);
throw new MakeLDIFException(message);
}
else if (numEntries == 0)
{
Message message = WARN_MAKELDIF_BRANCH_SUBORDINATE_ZERO_ENTRIES.get(
lineNumber, dnString,
templateName);
warnings.add(message);
}
branch.addSubordinateTemplate(templateName, numEntries);
}
catch (NumberFormatException nfe)
{
Message message =
ERR_MAKELDIF_BRANCH_SUBORDINATE_CANT_PARSE_NUMENTRIES.
get(templateName, lineNumber, dnString);
throw new MakeLDIFException(message);
}
}
else
{
TemplateLine templateLine = parseTemplateLine(line, lowerLine,
lineNumber, branch, null,
tags, warnings);
branch.addExtraLine(templateLine);
}
}
return branch;
}
/**
* Parses the information contained in the provided set of lines as a MakeLDIF
* template definition.
*
*
* @param templateLines The set of lines containing the template
* definition.
* @param startLineNumber The line number in the template file on which the
* first of the template lines appears.
* @param tags The set of defined tags from the template file.
* Note that this does not include the tags that are
* always registered by default.
* @param definedTemplates The set of templates already defined in the
* template file.
* @param warnings A list into which any warnings identified may be
* placed.
*
* @return The decoded template definition.
*
* @throws InitializationException If a problem occurs while initializing
* any of the template elements.
*
* @throws MakeLDIFException If some other problem occurs during processing.
*/
private Template parseTemplateDefinition(String[] templateLines,
int startLineNumber,
Map<String, Tag> tags,
Map<String, Template>
definedTemplates,
List<Message> warnings)
throws InitializationException, MakeLDIFException
{
// The first line must be "template: " followed by the template name.
String templateName = templateLines[0].substring(10).trim();
// The next line may start with either "extends: ", "rdnAttr: ", or
// "subordinateTemplate: ". Keep reading until we find something that's
// not one of those.
int arrayLineNumber = 1;
Template parentTemplate = null;
AttributeType[] rdnAttributes = null;
ArrayList<String> subTemplateNames = new ArrayList<String>();
ArrayList<Integer> entriesPerTemplate = new ArrayList<Integer>();
for ( ; arrayLineNumber < templateLines.length; arrayLineNumber++)
{
int lineNumber = startLineNumber + arrayLineNumber;
String line = templateLines[arrayLineNumber];
String lowerLine = toLowerCase(line);
if (lowerLine.startsWith("#"))
{
// It's a comment. Ignore it.
continue;
}
else if (lowerLine.startsWith("extends: "))
{
String parentTemplateName = line.substring(9).trim();
parentTemplate = definedTemplates.get(parentTemplateName.toLowerCase());
if (parentTemplate == null)
{
Message message = ERR_MAKELDIF_TEMPLATE_INVALID_PARENT_TEMPLATE.get(
parentTemplateName, lineNumber, templateName);
throw new MakeLDIFException(message);
}
}
else if (lowerLine.startsWith("rdnattr: "))
{
// This is the set of RDN attributes. If there are multiple, they may
// be separated by plus signs.
ArrayList<AttributeType> attrList = new ArrayList<AttributeType>();
String rdnAttrNames = lowerLine.substring(9).trim();
StringTokenizer tokenizer = new StringTokenizer(rdnAttrNames, "+");
while (tokenizer.hasMoreTokens())
{
attrList.add(DirectoryServer.getAttributeType(tokenizer.nextToken(),
true));
}
rdnAttributes = new AttributeType[attrList.size()];
attrList.toArray(rdnAttributes);
}
else if (lowerLine.startsWith("subordinatetemplate: "))
{
// It's a subordinate template, so we'll want to parse the name and the
// number of entries.
int colonPos = line.indexOf(':', 21);
if (colonPos <= 21)
{
Message message = ERR_MAKELDIF_TEMPLATE_SUBORDINATE_TEMPLATE_NO_COLON.
get(lineNumber, templateName);
throw new MakeLDIFException(message);
}
String subTemplateName = line.substring(21, colonPos).trim();
int numEntries;
try
{
numEntries = Integer.parseInt(line.substring(colonPos+1).trim());
if (numEntries < 0)
{
Message message =
ERR_MAKELDIF_TEMPLATE_SUBORDINATE_INVALID_NUM_ENTRIES.
get(lineNumber, templateName, numEntries, subTemplateName);
throw new MakeLDIFException(message);
}
else if (numEntries == 0)
{
Message message = WARN_MAKELDIF_TEMPLATE_SUBORDINATE_ZERO_ENTRIES
.get(lineNumber, templateName, subTemplateName);
warnings.add(message);
}
subTemplateNames.add(subTemplateName);
entriesPerTemplate.add(numEntries);
}
catch (NumberFormatException nfe)
{
Message message =
ERR_MAKELDIF_TEMPLATE_SUBORDINATE_CANT_PARSE_NUMENTRIES.
get(subTemplateName, lineNumber, templateName);
throw new MakeLDIFException(message);
}
}
else
{
// It's something we don't recognize, so it must be a template line.
break;
}
}
// Create a new template that will be used for the verification process.
String[] subordinateTemplateNames = new String[subTemplateNames.size()];
subTemplateNames.toArray(subordinateTemplateNames);
int[] numEntriesPerTemplate = new int[entriesPerTemplate.size()];
for (int i=0; i < numEntriesPerTemplate.length; i++)
{
numEntriesPerTemplate[i] = entriesPerTemplate.get(i);
}
TemplateLine[] parsedLines;
if (parentTemplate == null)
{
parsedLines = new TemplateLine[0];
}
else
{
TemplateLine[] parentLines = parentTemplate.getTemplateLines();
parsedLines = new TemplateLine[parentLines.length];
System.arraycopy(parentLines, 0, parsedLines, 0, parentLines.length);
}
Template template = new Template(this, templateName, rdnAttributes,
subordinateTemplateNames,
numEntriesPerTemplate, parsedLines);
for ( ; arrayLineNumber < templateLines.length; arrayLineNumber++)
{
String line = templateLines[arrayLineNumber];
String lowerLine = toLowerCase(line);
int lineNumber = startLineNumber + arrayLineNumber;
if (lowerLine.startsWith("#"))
{
// It's a comment, so we should ignore it.
continue;
}
else
{
TemplateLine templateLine = parseTemplateLine(line, lowerLine,
lineNumber, null,
template, tags, warnings);
template.addTemplateLine(templateLine);
}
}
return template;
}
/**
* Parses the provided line as a template line. Note that exactly one of the
* branch or template arguments must be non-null and the other must be null.
*
* @param line The text of the template line.
* @param lowerLine The template line in all lowercase characters.
* @param lineNumber The line number on which the template line appears.
* @param branch The branch with which the template line is associated.
* @param template The template with which the template line is
* associated.
* @param tags The set of defined tags from the template file. Note
* that this does not include the tags that are always
* registered by default.
* @param warnings A list into which any warnings identified may be
* placed.
*
* @return The template line that has been parsed.
*
* @throws InitializationException If a problem occurs while initializing
* any of the template elements.
*
* @throws MakeLDIFException If some other problem occurs during processing.
*/
private TemplateLine parseTemplateLine(String line, String lowerLine,
int lineNumber, Branch branch,
Template template,
Map<String,Tag> tags,
List<Message> warnings)
throws InitializationException, MakeLDIFException
{
// The first component must be the attribute type, followed by a colon.
int colonPos = lowerLine.indexOf(':');
if (colonPos < 0)
{
if (branch == null)
{
Message message = ERR_MAKELDIF_NO_COLON_IN_TEMPLATE_LINE.get(
lineNumber, template.getName());
throw new MakeLDIFException(message);
}
else
{
Message message = ERR_MAKELDIF_NO_COLON_IN_BRANCH_EXTRA_LINE.get(
lineNumber, String.valueOf(branch.getBranchDN()));
throw new MakeLDIFException(message);
}
}
else if (colonPos == 0)
{
if (branch == null)
{
Message message = ERR_MAKELDIF_NO_ATTR_IN_TEMPLATE_LINE.get(
lineNumber, template.getName());
throw new MakeLDIFException(message);
}
else
{
Message message = ERR_MAKELDIF_NO_ATTR_IN_BRANCH_EXTRA_LINE.get(
lineNumber, String.valueOf(branch.getBranchDN()));
throw new MakeLDIFException(message);
}
}
AttributeType attributeType =
DirectoryServer.getAttributeType(lowerLine.substring(0, colonPos),
true);
// First, check whether the value is an URL value: <attrName>:< <url>
int length = line.length();
int pos = colonPos + 1;
boolean valueIsURL = false;
boolean valueIsBase64 = false;
if (pos < length)
{
if (lowerLine.charAt(pos) == '<')
{
valueIsURL = true;
pos ++;
}
else if (lowerLine.charAt(pos) == ':')
{
valueIsBase64 = true;
pos ++;
}
}
// Then, find the position of the first non-blank character in the line.
while ((pos < length) && (lowerLine.charAt(pos) == ' '))
{
pos++;
}
if (pos >= length)
{
// We've hit the end of the line with no value. We'll allow it, but add a
// warning.
if (branch == null)
{
Message message = WARN_MAKELDIF_NO_VALUE_IN_TEMPLATE_LINE.get(
lineNumber, template.getName());
warnings.add(message);
}
else
{
Message message = WARN_MAKELDIF_NO_VALUE_IN_BRANCH_EXTRA_LINE.get(
lineNumber, String.valueOf(branch.getBranchDN()));
warnings.add(message);
}
}
// Define constants that specify what we're currently parsing.
final int PARSING_STATIC_TEXT = 0;
final int PARSING_REPLACEMENT_TAG = 1;
final int PARSING_ATTRIBUTE_TAG = 2;
final int PARSING_ESCAPED_CHAR = 3;
int phase = PARSING_STATIC_TEXT;
int previousPhase = PARSING_STATIC_TEXT;
ArrayList<Tag> tagList = new ArrayList<Tag>();
StringBuilder buffer = new StringBuilder();
for ( ; pos < length; pos++)
{
char c = line.charAt(pos);
switch (phase)
{
case PARSING_STATIC_TEXT:
switch (c)
{
case '\\':
phase = PARSING_ESCAPED_CHAR;
previousPhase = PARSING_STATIC_TEXT;
break;
case '<':
if (buffer.length() > 0)
{
StaticTextTag t = new StaticTextTag();
String[] args = new String[] { buffer.toString() };
t.initializeForBranch(this, branch, args, lineNumber,
warnings);
tagList.add(t);
buffer = new StringBuilder();
}
phase = PARSING_REPLACEMENT_TAG;
break;
case '{':
if (buffer.length() > 0)
{
StaticTextTag t = new StaticTextTag();
String[] args = new String[] { buffer.toString() };
t.initializeForBranch(this, branch, args, lineNumber,
warnings);
tagList.add(t);
buffer = new StringBuilder();
}
phase = PARSING_ATTRIBUTE_TAG;
break;
default:
buffer.append(c);
}
break;
case PARSING_REPLACEMENT_TAG:
switch (c)
{
case '\\':
phase = PARSING_ESCAPED_CHAR;
previousPhase = PARSING_REPLACEMENT_TAG;
break;
case '>':
Tag t = parseReplacementTag(buffer.toString(), branch, template,
lineNumber, tags, warnings);
tagList.add(t);
buffer = new StringBuilder();
phase = PARSING_STATIC_TEXT;
break;
default:
buffer.append(c);
break;
}
break;
case PARSING_ATTRIBUTE_TAG:
switch (c)
{
case '\\':
phase = PARSING_ESCAPED_CHAR;
previousPhase = PARSING_ATTRIBUTE_TAG;
break;
case '}':
Tag t = parseAttributeTag(buffer.toString(), branch, template,
lineNumber, warnings);
tagList.add(t);
buffer = new StringBuilder();
phase = PARSING_STATIC_TEXT;
break;
default:
buffer.append(c);
break;
}
break;
case PARSING_ESCAPED_CHAR:
buffer.append(c);
phase = previousPhase;
break;
}
}
if (phase == PARSING_STATIC_TEXT)
{
if (buffer.length() > 0)
{
StaticTextTag t = new StaticTextTag();
String[] args = new String[] { buffer.toString() };
t.initializeForBranch(this, branch, args, lineNumber, warnings);
tagList.add(t);
}
}
else
{
Message message = ERR_MAKELDIF_INCOMPLETE_TAG.get(lineNumber);
throw new InitializationException(message);
}
Tag[] tagArray = new Tag[tagList.size()];
tagList.toArray(tagArray);
return new TemplateLine(attributeType, lineNumber, tagArray, valueIsURL,
valueIsBase64);
}
/**
* Parses the provided string as a replacement tag. Exactly one of the branch
* or template must be null, and the other must be non-null.
*
* @param tagString The string containing the encoded tag.
* @param branch The branch in which this tag appears.
* @param template The template in which this tag appears.
* @param lineNumber The line number on which this tag appears in the
* template file.
* @param tags The set of defined tags from the template file. Note
* that this does not include the tags that are always
* registered by default.
* @param warnings A list into which any warnings identified may be
* placed.
*
* @return The replacement tag parsed from the provided string.
*
* @throws InitializationException If a problem occurs while initializing
* the tag.
*
* @throws MakeLDIFException If some other problem occurs during processing.
*/
private Tag parseReplacementTag(String tagString, Branch branch,
Template template, int lineNumber,
Map<String,Tag> tags,
List<Message> warnings)
throws InitializationException, MakeLDIFException
{
// The components of the replacement tag will be separated by colons, with
// the first being the tag name and the remainder being arguments.
StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
String tagName = tokenizer.nextToken().trim();
String lowerTagName = toLowerCase(tagName);
Tag t = getTag(lowerTagName);
if (t == null)
{
t = tags.get(lowerTagName);
if (t == null)
{
Message message = ERR_MAKELDIF_NO_SUCH_TAG.get(tagName, lineNumber);
throw new MakeLDIFException(message);
}
}
ArrayList<String> argList = new ArrayList<String>();
while (tokenizer.hasMoreTokens())
{
argList.add(tokenizer.nextToken().trim());
}
String[] args = new String[argList.size()];
argList.toArray(args);
Tag newTag;
try
{
newTag = t.getClass().newInstance();
}
catch (Exception e)
{
Message message = ERR_MAKELDIF_CANNOT_INSTANTIATE_NEW_TAG.get(
tagName, lineNumber, String.valueOf(e));
throw new MakeLDIFException(message, e);
}
if (branch == null)
{
newTag.initializeForTemplate(this, template, args, lineNumber, warnings);
}
else
{
if (newTag.allowedInBranch())
{
newTag.initializeForBranch(this, branch, args, lineNumber, warnings);
}
else
{
Message message = ERR_MAKELDIF_TAG_NOT_ALLOWED_IN_BRANCH.get(
newTag.getName(), lineNumber);
throw new MakeLDIFException(message);
}
}
return newTag;
}
/**
* Parses the provided string as an attribute tag. Exactly one of the branch
* or template must be null, and the other must be non-null.
*
* @param tagString The string containing the encoded tag.
* @param branch The branch in which this tag appears.
* @param template The template in which this tag appears.
* @param lineNumber The line number on which this tag appears in the
* template file.
* @param warnings A list into which any warnings identified may be
* placed.
*
* @return The attribute tag parsed from the provided string.
*
* @throws InitializationException If a problem occurs while initializing
* the tag.
*
* @throws MakeLDIFException If some other problem occurs during processing.
*/
private Tag parseAttributeTag(String tagString, Branch branch,
Template template, int lineNumber,
List<Message> warnings)
throws InitializationException, MakeLDIFException
{
// The attribute tag must have at least one argument, which is the name of
// the attribute to reference. It may have a second argument, which is the
// number of characters to use from the attribute value. The arguments will
// be delimited by colons.
StringTokenizer tokenizer = new StringTokenizer(tagString, ":");
ArrayList<String> argList = new ArrayList<String>();
while (tokenizer.hasMoreTokens())
{
argList.add(tokenizer.nextToken());
}
String[] args = new String[argList.size()];
argList.toArray(args);
AttributeValueTag tag = new AttributeValueTag();
if (branch == null)
{
tag.initializeForTemplate(this, template, args, lineNumber, warnings);
}
else
{
tag.initializeForBranch(this, branch, args, lineNumber, warnings);
}
return tag;
}
/**
* Retrieves a File object based on the provided path. If the given path is
* absolute, then that absolute path will be used. If it is relative, then it
* will first be evaluated relative to the current working directory. If that
* path doesn't exist, then it will be evaluated relative to the resource
* path. If that path doesn't exist, then it will be evaluated relative to
* the directory containing the template file.
*
* @param path The path provided for the file.
*
* @return The File object for the specified path, or <CODE>null</CODE> if
* the specified file could not be found.
*/
public File getFile(String path)
{
// First, see if the file exists using the given path. This will work if
// the file is absolute, or it's relative to the current working directory.
File f = new File(path);
if (f.exists())
{
return f;
}
// If the provided path was absolute, then use it anyway, even though we
// couldn't find the file.
if (f.isAbsolute())
{
return f;
}
// Try a path relative to the resource directory.
String newPath = resourcePath + File.separator + path;
f = new File(newPath);
if (f.exists())
{
return f;
}
// Try a path relative to the template directory, if it's available.
if (templatePath != null)
{
newPath = templatePath = File.separator + path;
f = new File(newPath);
if (f.exists())
{
return f;
}
}
return null;
}
/**
* Retrieves the lines of the specified file as a string array. If the result
* is already cached, then it will be used. If the result is not cached, then
* the file data will be cached so that the contents can be re-used if there
* are multiple references to the same file.
*
* @param file The file for which to retrieve the contents.
*
* @return An array containing the lines of the specified file.
*
* @throws IOException If a problem occurs while reading the file.
*/
public String[] getFileLines(File file)
throws IOException
{
String absolutePath = file.getAbsolutePath();
String[] lines = fileLines.get(absolutePath);
if (lines == null)
{
ArrayList<String> lineList = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new FileReader(file));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
else
{
lineList.add(line);
}
}
reader.close();
lines = new String[lineList.size()];
lineList.toArray(lines);
lineList.clear();
fileLines.put(absolutePath, lines);
}
return lines;
}
/**
* Generates the LDIF content and writes it to the provided LDIF writer.
*
* @param entryWriter The entry writer that should be used to write the
* entries.
*
* @return The result that indicates whether processing should continue.
*
* @throws IOException If an error occurs while writing to the LDIF file.
*
* @throws MakeLDIFException If some other problem occurs.
*/
public TagResult generateLDIF(EntryWriter entryWriter)
throws IOException, MakeLDIFException
{
for (Branch b : branches.values())
{
TagResult result = b.writeEntries(entryWriter);
if (! (result.keepProcessingTemplateFile()))
{
return result;
}
}
entryWriter.closeEntryWriter();
return TagResult.SUCCESS_RESULT;
}
}