diff options
Diffstat (limited to 'qpid/java/broker/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java')
-rw-r--r-- | qpid/java/broker/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/qpid/java/broker/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java b/qpid/java/broker/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java new file mode 100644 index 0000000000..a39799a6b6 --- /dev/null +++ b/qpid/java/broker/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java @@ -0,0 +1,527 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ + +package org.apache.qpid.server.logging; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.File; +import java.io.FileWriter; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; + +public class GenerateLogMessages +{ + private boolean DEBUG = false; + private String _tmplDir; + private String _outputDir; + private List<String> _logMessages = new LinkedList<String>(); + private String _packageSource; + + public static void main(String[] args) + { + GenerateLogMessages generator = null; + try + { + generator = new GenerateLogMessages(args); + } + catch (IllegalAccessException iae) + { + // This occurs when args does not contain Template and output dirs. + System.exit(-1); + } + catch (Exception e) + { + //This is thrown by the Velocity Engine initialisation + e.printStackTrace(); + System.exit(-1); + } + + try + { + System.out.println("Running LogMessage Generator"); + generator.run(); + } + catch (InvalidTypeException e) + { + // This occurs when a type other than 'number' appears in the + // paramater config {0, number...} + System.err.println(e.getMessage()); + System.exit(-1); + } + catch (Exception e) + { + e.printStackTrace(); + System.exit(-1); + } + } + + GenerateLogMessages(String[] args) throws Exception + { + processArgs(args); + + // We need the template and input files to run. + if (_tmplDir == null || _outputDir == null || _logMessages.size() == 0 || _packageSource == null) + { + showUsage(); + throw new IllegalAccessException(); + } + + // Initialise the Velocity Engine, Telling it where our macro lives + Properties props = new Properties(); + props.setProperty("file.resource.loader.path", _tmplDir); + Velocity.init(props); + } + + private void showUsage() + { + System.out.println("Broker LogMessageGenerator v.2.0"); + System.out.println("Usage: GenerateLogMessages: [-d] -t <Template Dir> -o <Output Root> -s <Source Directory> <List of _logmessage.property files to process>"); + System.out.println(" where -d : Additional debug loggin.\n" + + " Template Dir: Is the base template to use."); + System.out.println(" Output Root: The root form where the LogMessage Classes will be placed."); + System.out.println(" Source Dir : The root form where the logmessasge.property files can be found."); + System.out.println(" _logmessage.property files must include the full package structure."); + } + + public void run() throws InvalidTypeException, Exception + { + for (String file : _logMessages) + { + debug("Processing File:" + file); + + createMessageClass(file); + } + } + + /** + * Process the args for: + * -t|T value for the template location + * -o|O value for the output directory + * + * @param args the commandline arguments + */ + private void processArgs(String[] args) + { + // Crude but simple... + for (int i = 0; i < args.length; i++) + { + String arg = args[i]; + if (args[i].endsWith("_logmessages.properties")) + { + _logMessages.add(args[i]); + } + else if (arg.charAt(0) == '-') + { + switch (arg.charAt(1)) + { + case 'o': + case 'O': + if (++i < args.length) + { + _outputDir = args[i]; + } + break; + case 't': + case 'T': + if (++i < args.length) + { + _tmplDir = args[i]; + } + break; + case 's': + case 'S': + if (++i < args.length) + { + _packageSource = args[i]; + } + break; + case 'd': + case 'D': + DEBUG=true; + break; + } + } + } + } + + /** + * This is the main method that generates the _Messages.java file. + * The class is generated by extracting the list of messges from the + * available LogMessages Resource. + * + * The extraction is done based on typeIdentifier which is a 3-digit prefix + * on the messages e.g. BRK for Broker. + * + * @throws InvalidTypeException when an unknown parameter type is used in the properties file + * @throws Exception thrown by velocity if there is an error + */ + private void createMessageClass(String file) + throws InvalidTypeException, Exception + { + VelocityContext context = new VelocityContext(); + + String bundle = file.replace(File.separator, "."); + + int packageStartIndex = bundle.indexOf(_packageSource) + _packageSource.length() + 2; + + bundle = bundle.substring(packageStartIndex, bundle.indexOf(".properties")); + + System.out.println("Creating Class for bundle:" + bundle); + + ResourceBundle fileBundle = ResourceBundle.getBundle(bundle, Locale.US); + + // Pull the bit from /os/path/<className>.logMessages from the bundle name + String className = file.substring(file.lastIndexOf(File.separator) + 1, file.lastIndexOf("_")); + debug("Creating ClassName form file:" + className); + + String packageString = bundle.substring(0, bundle.indexOf(className)); + String packagePath = packageString.replace(".", File.separator); + + debug("Package path:" + packagePath); + + File outputDirectory = new File(_outputDir + File.separator + packagePath); + if (!outputDirectory.exists()) + { + if (!outputDirectory.mkdirs()) + { + throw new IllegalAccessException("Unable to create package structure:" + outputDirectory); + } + } + + // Get the Data for this class and typeIdentifier + HashMap<String, Object> typeData = prepareType(className, fileBundle); + + context.put("package", packageString.substring(0, packageString.lastIndexOf('.'))); + //Store the resource Bundle name for the macro + context.put("resource", bundle); + + // Store this data in the context for the macro to access + context.put("type", typeData); + + // Create the file writer to put the finished file in + String outputFile = _outputDir + File.separator + packagePath + className + "Messages.java"; + debug("Creating Java file:" + outputFile); + FileWriter output = new FileWriter(outputFile); + + // Run Velocity to create the output file. + // Fix the default file encoding to 'ISO-8859-1' rather than let + // Velocity fix it. This is the encoding format for the macro. + Velocity.mergeTemplate("LogMessages.vm", "ISO-8859-1", context, output); + + //Close our file. + output.flush(); + output.close(); + } + + /** + * This method does the processing and preparation of the data to be added + * to the Velocity context so that the macro can access and process the data + * + * The given messageKey (e.g. 'BRK') uses a 3-digit code used to match + * the property key from the loaded 'LogMessages' ResourceBundle. + * + * This gives a list of messages which are to be associated with the given + * messageName (e.g. 'Broker') + * + * Each of the messages in the list are then processed to identify how many + * parameters the MessageFormat call is expecting. These parameters are + * identified by braces ('{}') so a quick count of these can then be used + * to create a defined parameter list. + * + * Rather than defaulting all parameters to String a check is performed to + * see if a 'number' value has been requested. e.g. {0. number} + * {@see MessageFormat}. If a parameter has a 'number' type then the + * parameter will be defined as an Number value. This allows for better + * type checking during compilation whilst allowing the log message to + * maintain formatting controls. + * + * OPTIONS + * + * The returned hashMap contains the following structured data: + * + * - name - ClassName ('Broker') + * - list[logEntryData] - methodName ('BRK_1001') + * - name ('BRK-1001') + * - format ('Startup : Version: {0} Build: {1}') + * - parameters (list) + * - type ('String'|'Number') + * - name ('param1') + * - options (list) + * - name ('opt1') + * - value ('AutoDelete') + * + * @param messsageName the name to give the Class e.g. 'Broker' + * + * @return A HashMap with data for the macro + * + * @throws InvalidTypeException when an unknown parameter type is used in the properties file + */ + private HashMap<String, Object> prepareType(String messsageName, ResourceBundle messages) throws InvalidTypeException + { + // Load the LogMessages Resource Bundle + Enumeration<String> messageKeys = messages.getKeys(); + + //Create the return map + HashMap<String, Object> messageTypeData = new HashMap<String, Object>(); + // Store the name to give to this Class <name>Messages.java + messageTypeData.put("name", messsageName); + + // Prepare the list of log messages + List<HashMap> logMessageList = new LinkedList<HashMap>(); + messageTypeData.put("list", logMessageList); + + //Process each of the properties + while (messageKeys.hasMoreElements()) + { + HashMap<String, Object> logEntryData = new HashMap<String, Object>(); + + //Add MessageName to amp + String message = messageKeys.nextElement(); + + // Process the log message if it matches the specified key e.g.'BRK_' + if (!message.equals("package")) + { + // Method names can't have a '-' in them so lets make it '_' + // e.g. RECOVERY-STARTUP -> RECOVERY_STARTUP + logEntryData.put("methodName", message.replace('-', '_')); + // Store the real name so we can use that in the actual log. + logEntryData.put("name", message); + + //Retrieve the actual log message string. + String logMessage = messages.getString(message); + + // Store the value of the property so we can add it to the + // Javadoc of the method. + logEntryData.put("format", logMessage); + + // Add the parameters for this message + logEntryData.put("parameters", extractParameters(logMessage)); + + //Add the options for this messages + logEntryData.put("options", extractOptions(logMessage)); + + //Add this entry to the list for this class + logMessageList.add(logEntryData); + } + } + + return messageTypeData; + } + + /** + * This method is responsible for extracting the options form the given log + * message and providing a HashMap with the expected data: + * - options (list) + * - name ('opt1') + * - value ('AutoDelete') + * + * The options list that this method provides must contain HashMaps that + * have two entries. A 'name' and a 'value' these strings are important as + * they are used in LogMessage.vm to extract the stored String during + * processing of the template. + * + * The 'name' is a unique string that is used to name the boolean parameter + * and refer to it later in the method body. + * + * The 'value' is used to provide documentation in the generated class to + * aid readability. + * + * @param logMessage the message from the properties file + * + * @return list of option data + */ + private List<HashMap<String, String>> extractOptions(String logMessage) + { + // Split the string on the start brace '{' this will give us the + // details for each parameter that this message contains. + // NOTE: Simply splitting on '[' prevents the use of nested options. + // Currently there is no demand for nested options + String[] optionString = logMessage.split("\\["); + // Taking an example of: + // 'Text {n,type,format} [option] text {m} [option with param{p}] more' + // This would give us: + // 0 - Text {n,type,format} + // 1 - option] text {m} + // 2 - option with param{p}] more + + // Create the parameter list for this item + List<HashMap<String, String>> options = new LinkedList<HashMap<String, String>>(); + + // Check that we have some parameters to process + // Skip 0 as that will not be the first entry + // Text {n,type,format} [option] text {m} [option with param{p}] more + if (optionString.length > 1) + { + for (int index = 1; index < optionString.length; index++) + { + // Use a HashMap to store the type,name of the parameter + // for easy retrieval in the macro template + HashMap<String, String> option = new HashMap<String, String>(); + + // Locate the end of the Option + // NOTE: it is this simple matching on the first ']' that + // prevents the nesting of options. + // Currently there is no demand for nested options + int end = optionString[index].indexOf("]"); + + // The parameter type + String value = optionString[index].substring(0, end); + + // Simply name the parameters by index. + option.put("name", "opt" + index); + + //Store the value e.g. AutoDelete + // We trim as we don't need to include any whitespace that is + // used for formating. This is only used to aid readability of + // of the generated code. + option.put("value", value.trim()); + + options.add(option); + } + } + + return options; + } + + /** + * This method is responsible for extract the parameters that are requried + * for this log message. The data is returned in a HashMap that has the + * following structure: + * - parameters (list) + * - type ('String'|'Number') + * - name ('param1') + * + * The parameters list that is provided must contain HashMaps that have the + * two named entries. A 'type' and a 'name' these strings are importans as + * they are used in LogMessage.vm to extract and the stored String during + * processing of the template + * + * The 'type' and 'name' values are used to populate the method signature. + * This is what gives us the compile time validation of log messages. + * + * The 'name' must be unique as there may be many parameters. The value is + * also used in the method body to pass the values through to the + * MessageFormat class for formating the log message. + * + * @param logMessage the message from the properties file + * + * @return list of option data + * + * @throws InvalidTypeException if the FormatType is specified and is not 'number' + */ + private List<HashMap<String, String>> extractParameters(String logMessage) + throws InvalidTypeException + { + // Split the string on the start brace '{' this will give us the + // details for each parameter that this message contains. + String[] parametersString = logMessage.split("\\{"); + // Taking an example of 'Text {n[,type]} text {m} more text {p}' + // This would give us: + // 0 - Text + // 1 - n[,type]} text + // 2 - m} more text + // 3 - p} + + // Create the parameter list for this item + List<HashMap<String, String>> parameters = new LinkedList<HashMap<String, String>>(); + + // Check that we have some parameters to process + // Skip 0 as that will not be the first entry + // Text {n[,type]} text {m} more text {p} + if (parametersString.length > 1) + { + for (int index = 1; index < parametersString.length; index++) + { + // Use a HashMap to store the type,name of the parameter + // for easy retrieval in the macro template + HashMap<String, String> parameter = new HashMap<String, String>(); + + // Check for any properties of the parameter : + // e.g. {0} vs {0,number} vs {0,number,xxxx} + int typeIndex = parametersString[index].indexOf(","); + + // The parameter type + String type; + + //Be default all types are Strings + if (typeIndex == -1) + { + type = "String"; + } + else + { + //Check string ',....}' for existence of number + // to identify this parameter as an integer + // This allows for a style value to be present + // Only check the text inside the braces '{}' + int typeIndexEnd = parametersString[index].indexOf("}", typeIndex); + String typeString = parametersString[index].substring(typeIndex, typeIndexEnd); + if (typeString.contains("number") || typeString.contains("choice")) + { + type = "Number"; + } + else + { + throw new InvalidTypeException("Invalid type(" + typeString + ") index (" + parameter.size() + ") in message:" + logMessage); + } + + } + + //Store the data + parameter.put("type", type); + // Simply name the parameters by index. + parameter.put("name", "param" + index); + + parameters.add(parameter); + } + } + + return parameters; + } + + /** + * Just a inner exception to be able to identify when a type that is not + * 'number' occurs in the message parameter text. + */ + private static class InvalidTypeException extends Exception + { + public InvalidTypeException(String message) + { + super(message); + } + } + + public void debug(String msg) + { + if (DEBUG) + { + System.out.println(msg); + } + } + +} |