Java tutorial
/* * Copyright 2005-2013 the original author or authors. * * Licensed 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.springframework.ldap.odm.tools; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; import freemarker.template.TemplateException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.PosixParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This tool creates a Java class representation of a set of LDAP object classes for use * with {@link org.springframework.ldap.odm.core.OdmManager}. * <p> * The schema of a named list of object classes is read from an LDAP directory and used * to generate a representative Java class. The Java class is automatically annotated with * {@link org.springframework.ldap.odm.annotations} for use with * {@link org.springframework.ldap.odm.core.OdmManager}. * <p> * The mapping of LDAP attributes to their Java representations may be configured by supplying the * <code>-s</code> flag or the equivalent <code>--syntaxmap</code> flag whose argument is * the name of a file with the following structure: * <pre> * # List of attribute syntax to java class mappings * * # Syntax Java class * # ------ ---------- * * 1.3.6.1.4.1.1466.115.121.1.50, java.lang.Integer * 1.3.6.1.4.1.1466.115.121.1.40, some.other.Class * </pre> * <p> * Syntaxes not included in this map will be represented as {@link java.lang.String} if they are returned as Strings by the * JNDI LDAP provider and will be represented as <code>byte[]</code> if they are returned by the provider as <code>byte[]</code>. * <p> * Command line flags are as follows: * <p> * <ul> * <li><code>-c,--class <class name></code> Name of the Java class to create. Mandatory.</li> * <li><code>-s,--syntaxmap <map file></code> Configuration file of LDAP syntaxes to Java classes mappings. Optional.</li> * <li><code>-h,--help</code> Print this help message then exit.</li> * <li><code>-k,--package <package name></code> Package to create the Java class in. Mandatory.</li> * <li><code>-l,--url <ldap url></code> Ldap url of the directory service to bind to. Defaults to <code>ldap://127.0.0.1:389</code>. Optional.</li> * <li><code>-o,--objectclasses <LDAP object class lists></code> Comma separated list of LDAP object classes. Mandatory.</li> * <li><code>-u,--username <dn></code> DN to bind with. Defaults to "". Optional.</li> * <li><code>-p,--password <password></code> Password to bind with. Defaults to "". Optional.</li> * <li><code>-t,--outputdir <output directory></code> Base output directory, defaults to ".". Optional.</li> * </ul> * * @author Paul Harvey <paul.at.pauls-place.me.uk> */ public final class SchemaToJava { private static final Logger LOG = LoggerFactory.getLogger(SchemaToJava.class); // Name of the FreeMarker template used to generate the Java code. private static final String TEMPLATE_FILE = "oc-to-java.ftl"; // Name of file containing the list of attributes syntaxes to // returned as byte[] by the JNDI LDAP provider. private static final String BINARY_FILE = "binary-attributes.txt"; // Class to use a base for loading resources private static final Class<?> DEFAULT_LOADER_CLASS = SchemaToJava.class; // Default LDAP Url to bind with private static final String DEFAULT_URL = "ldap://127.0.0.1:389"; // Command line flags private enum Flag { URL("l", "url"), USERNAME("u", "username"), PASSWORD("p", "password"), OBJECTCLASS("o", "objectclasses"), CLASS("c", "class"), PACKAGE("k", "package"), SYNTAX_MAP("s", "syntaxmap"), OUTPUT_DIR("t", "outputdir"), HELP("h", "help"); private String shortName; private String longName; private Flag(String shortName, String longName) { this.shortName = shortName; this.longName = longName; } public String getShort() { return shortName; } public String getLong() { return longName; } @Override public String toString() { return String.format("short=%1$s, long=%2$s", shortName, longName); } } private static final Options DEFAULT_OPTIONS = new Options(); static { DEFAULT_OPTIONS.addOption(Flag.URL.getShort(), Flag.URL.getLong(), true, "Ldap url (defaults to " + DEFAULT_URL + ")"); DEFAULT_OPTIONS.addOption(Flag.USERNAME.getShort(), Flag.USERNAME.getLong(), true, "DN to bind with (defaults to \"\""); DEFAULT_OPTIONS.addOption(Flag.PASSWORD.getShort(), Flag.PASSWORD.getLong(), true, "Password to bind with (defaults to \"\""); DEFAULT_OPTIONS.addOption(Flag.OBJECTCLASS.getShort(), Flag.OBJECTCLASS.getLong(), true, "Comma separated list of object classes"); DEFAULT_OPTIONS.addOption(Flag.CLASS.getShort(), Flag.CLASS.getLong(), true, "Name of the Java class to create"); DEFAULT_OPTIONS.addOption(Flag.PACKAGE.getShort(), Flag.PACKAGE.getLong(), true, "Package to create the Java class in"); DEFAULT_OPTIONS.addOption(Flag.SYNTAX_MAP.getShort(), Flag.SYNTAX_MAP.getLong(), true, "Syntax map file (optional)"); DEFAULT_OPTIONS.addOption(Flag.OUTPUT_DIR.getShort(), Flag.OUTPUT_DIR.getLong(), true, "Base output directory (defaults to .)"); DEFAULT_OPTIONS.addOption(Flag.HELP.getShort(), Flag.HELP.getLong(), false, "Print this help message"); } /** * Not to be instantiated. */ private SchemaToJava() { } // Read list of LDAP syntaxes that are returned as byte[] private static Set<String> readBinarySet(File binarySetFile) throws IOException { Set<String> result = new HashSet<String>(); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(binarySetFile)); String line; while ((line = reader.readLine()) != null) { String trimmed = line.trim(); if (trimmed.length() > 0) { if (trimmed.charAt(0) != '#') { String[] parts = trimmed.split("\\s"); if (parts.length > 0) { result.add(parts[0]); } } } } } finally { if (reader != null) { reader.close(); } } return result; } // Read mappings of LDAP syntaxes to Java classes. private static Map<String, String> readSyntaxMap(File syntaxMapFile) throws IOException { Map<String, String> result = new HashMap<String, String>(); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(syntaxMapFile)); String line; while ((line = reader.readLine()) != null) { String trimmed = line.trim(); if (trimmed.length() > 0) { if (trimmed.charAt(0) != '#') { String[] parts = trimmed.split(","); if (parts.length != 2) { throw new IOException(String.format("Failed to parse line \"%1$s\"", trimmed)); } String partOne = parts[0].trim(); String partTwo = parts[1].trim(); if (partOne.length() == 0 || partTwo.length() == 0) { throw new IOException(String.format("Failed to parse line \"%1$s\"", trimmed)); } result.put(partOne, partTwo); } } } } finally { if (reader != null) { reader.close(); } } return result; } // Bind to the directory, read and process the schema private static ObjectSchema readSchema(String url, String user, String pass, SyntaxToJavaClass syntaxToJavaClass, Set<String> binarySet, Set<String> objectClasses) throws NamingException, ClassNotFoundException { // Set up environment Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.PROVIDER_URL, url); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); if (user != null) { env.put(Context.SECURITY_PRINCIPAL, user); } if (pass != null) { env.put(Context.SECURITY_CREDENTIALS, pass); } DirContext context = new InitialDirContext(env); DirContext schemaContext = context.getSchema(""); SchemaReader reader = new SchemaReader(schemaContext, syntaxToJavaClass, binarySet); ObjectSchema schema = reader.getObjectSchema(objectClasses); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Schema - %1$s", schema.toString())); } return schema; } // Create the Java private static void createCode(String packageName, String className, ObjectSchema schema, Set<SyntaxToJavaClass.ClassInfo> imports, File outputFile) throws IOException, TemplateException { Configuration freeMarkerConfiguration = new Configuration(); freeMarkerConfiguration.setClassForTemplateLoading(DEFAULT_LOADER_CLASS, ""); freeMarkerConfiguration.setObjectWrapper(new DefaultObjectWrapper()); // Build the model for FreeMarker Map<String, Object> model = new HashMap<String, Object>(); model.put("package", packageName); model.put("class", className); model.put("schema", schema); model.put("imports", imports); // Have FreeMarker process the model with the template Template template = freeMarkerConfiguration.getTemplate(TEMPLATE_FILE); if (LOG.isDebugEnabled()) { Writer out = new OutputStreamWriter(System.out); template.process(model, out); out.flush(); } LOG.debug(String.format("Writing java to: %1$s", outputFile.getAbsolutePath())); FileOutputStream outputStream = new FileOutputStream(outputFile); Writer out = new OutputStreamWriter(outputStream); template.process(model, out); out.flush(); out.close(); } // Create the output file for the generated code along with all intervening directories private static File makeOutputFile(String outputDir, String packageName, String className) throws IOException { // Convert the package name to a path Pattern pattern = Pattern.compile("\\."); Matcher matcher = pattern.matcher(packageName); String sepToUse = File.separator; if (sepToUse.equals("\\")) { sepToUse = "\\\\"; } // Try to create the necessary directories String directoryPath = outputDir + File.separator + matcher.replaceAll(sepToUse); File directory = new File(directoryPath); File outputFile = new File(directory, className + ".java"); LOG.debug(String.format("Attempting to create output file at %1$s", outputFile.getAbsolutePath())); try { directory.mkdirs(); outputFile.createNewFile(); } catch (SecurityException se) { throw new IOException(String.format("Can't write to output file %1$s", outputFile.getAbsoluteFile()), se); } catch (IOException ioe) { throw new IOException(String.format("Can't write to output file %1$s", outputFile.getAbsoluteFile()), ioe); } return outputFile; } private static Set<String> parseObjectClassesFlag(String objectClassesFlag) { Set<String> objectClasses = new HashSet<String>(); for (String objectClassFlag : objectClassesFlag.split(",")) { if (objectClassFlag.length() > 0) { objectClasses.add(objectClassFlag.toLowerCase().trim()); } } return objectClasses; } private static void error(String message) { System.err.println(String.format("%1$s: %2$s", SchemaToJava.class.getSimpleName(), message)); System.exit(1); } public static void main(String[] argv) { CommandLineParser parser = new PosixParser(); CommandLine cmd = null; // Parse out the command line options try { cmd = parser.parse(DEFAULT_OPTIONS, argv); } catch (ParseException e) { error(e.toString()); } // If the help flag is specified ignore other flags, print a usage message and exit if (cmd.hasOption(Flag.HELP.getShort())) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp(120, SchemaToJava.class.getSimpleName(), null, DEFAULT_OPTIONS, null, true); System.exit(0); } // Class name flag String className = cmd.getOptionValue(Flag.CLASS.getShort()); if (className == null) { error("You must specify the name of a Java class to create"); } // Package name flag String packageName = cmd.getOptionValue(Flag.PACKAGE.getShort()); if (packageName == null) { error("You must specifiy a package name"); } // Output base directory String outputDir = cmd.getOptionValue(Flag.OUTPUT_DIR.getShort(), "."); File outputFile = null; try { outputFile = makeOutputFile(outputDir, packageName, className); } catch (IOException e) { error(e.toString()); } // Get the flags we need to bind to the directory String url = cmd.getOptionValue(Flag.URL.getShort(), DEFAULT_URL); String user = cmd.getOptionValue(Flag.USERNAME.getShort()); String pass = cmd.getOptionValue(Flag.PASSWORD.getShort()); // Parse out object classes String objectClassesFlag = cmd.getOptionValue(Flag.OBJECTCLASS.getShort()); if (objectClassesFlag == null) { error("You must specificy a package name"); } Set<String> objectClasses = parseObjectClassesFlag(objectClassesFlag); if (objectClasses.size() == 0) { error("You must specificy a package name"); } // Look for the optional syntax to Java class mapping file String syntaxMapFileName = cmd.getOptionValue(Flag.SYNTAX_MAP.getShort(), null); SyntaxToJavaClass syntaxToJavaClass = new SyntaxToJavaClass(new HashMap<String, String>()); if (syntaxMapFileName != null) { File syntaxMapFile = new File(syntaxMapFileName); if (syntaxMapFile.canRead()) { try { syntaxToJavaClass = new SyntaxToJavaClass(readSyntaxMap(syntaxMapFile)); } catch (IOException e) { error(String.format("Error reading syntax map file %1$s - %2$s", syntaxMapFile.getAbsolutePath(), e.toString())); } } else { error(String.format("Cannot read syntax map file %s$1", syntaxMapFile.getAbsolutePath())); } } // Read binary mapping file URL binarySetUrl = DEFAULT_LOADER_CLASS.getResource(BINARY_FILE); if (binarySetUrl == null) { error(String.format("Can't locatate binary mappings file %1$s", BINARY_FILE)); } File binarySetFile = new File(binarySetUrl.getFile()); if (!binarySetFile.canRead()) { error(String.format("Can't read from binary mappings file %1$s", BINARY_FILE)); } Set<String> binarySet = null; try { binarySet = readBinarySet(binarySetFile); } catch (IOException e) { error(String.format("Error reading binary set file %1$s - %2$s", binarySetFile.getAbsolutePath(), e)); } // Read schema from the directory ObjectSchema schema = null; try { schema = readSchema(url, user, pass, syntaxToJavaClass, binarySet, objectClasses); } catch (NamingException ne) { error(String.format("Error processing schema - %1$s", ne)); } catch (ClassNotFoundException cnfe) { error(String.format("Error processing schema - %1$s", cnfe)); } // Work out what imports we need Set<SyntaxToJavaClass.ClassInfo> imports = new HashSet<SyntaxToJavaClass.ClassInfo>(); for (AttributeSchema attributeSchema : schema.getMay()) { SyntaxToJavaClass.ClassInfo classInfo = syntaxToJavaClass.getClassInfo(attributeSchema.getSyntax()); if (classInfo != null) { String classPackageName = classInfo.getPackageName(); if (classPackageName != null && classPackageName.length() > 0) { imports.add(classInfo); } } } // Create the Java code try { createCode(packageName, className, schema, imports, outputFile); } catch (TemplateException te) { error(String.format("Error generating code - %1$s", te.toString())); } catch (IOException ioe) { error(String.format("Error generatign code - %1$s", ioe.toString())); } } }