/* * 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; } }