/** * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. * * This software is licensed under the GNU General Public License v3 or later. * * It is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package com.cloud.api.doc; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.TreeMap; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.log4j.Logger; import com.cloud.alert.AlertManager; import com.cloud.api.BaseAsyncCmd; import com.cloud.api.BaseAsyncCreateCmd; import com.cloud.api.BaseCmd; import com.cloud.api.BaseListCmd; import com.cloud.api.Implementation; import com.cloud.api.Parameter; import com.cloud.api.response.BaseResponse; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; import com.thoughtworks.xstream.XStream; public class ApiXmlDocWriter { public static final Logger s_logger = Logger.getLogger(ApiXmlDocWriter.class.getName()); private static final short DOMAIN_ADMIN_COMMAND = 2; private static final short USER_COMMAND = 8; private static LinkedHashMap all_api_commands = new LinkedHashMap(); private static LinkedHashMap domain_admin_api_commands = new LinkedHashMap(); private static LinkedHashMap regular_user_api_commands = new LinkedHashMap(); private static TreeMap all_api_commands_sorted = new TreeMap(); private static TreeMap domain_admin_api_commands_sorted = new TreeMap(); private static TreeMap regular_user_api_commands_sorted = new TreeMap(); private static String dirName=""; public static void main (String[] args) { LinkedProperties preProcessedCommands = new LinkedProperties(); String[] fileNames = null; List argsList = Arrays.asList(args); Iterator iter = argsList.iterator(); while (iter.hasNext()) { String arg = iter.next(); // populate the file names if (arg.equals("-f")) { fileNames = iter.next().split(","); } if (arg.equals("-d")) { dirName = iter.next(); } } if ((fileNames == null) || (fileNames.length == 0)){ System.out.println("Please specify input file(s) separated by coma using -f option"); System.exit(2); } for (String fileName : fileNames) { try { FileInputStream in = new FileInputStream(fileName); preProcessedCommands.load(in); }catch (FileNotFoundException ex) { System.out.println("Can't find file " + fileName); System.exit(2); } catch (IOException ex1) { System.out.println("Error reading from file " + ex1); System.exit(2); } } Iterator propertiesIterator = preProcessedCommands.keys.iterator(); //Get command classes and response object classes while (propertiesIterator.hasNext()) { String key = (String)propertiesIterator.next(); String preProcessedCommand = preProcessedCommands.getProperty(key); String[] commandParts = preProcessedCommand.split(";"); String commandName = commandParts[0]; all_api_commands.put(key, commandName); short cmdPermissions = 1; if (commandParts.length > 1 && commandParts[1] != null) { cmdPermissions = Short.parseShort(commandParts[1]); } if ((cmdPermissions & DOMAIN_ADMIN_COMMAND) != 0) { domain_admin_api_commands.put(key, commandName); } if ((cmdPermissions & USER_COMMAND) != 0) { regular_user_api_commands.put(key, commandName); } } //Login and logout commands are hardcoded all_api_commands.put("login", "login"); domain_admin_api_commands.put("login", "login"); regular_user_api_commands.put("login", "login"); all_api_commands.put("logout", "logout"); domain_admin_api_commands.put("logout", "logout"); regular_user_api_commands.put("logout", "logout"); all_api_commands_sorted.putAll(all_api_commands); domain_admin_api_commands_sorted.putAll(domain_admin_api_commands); regular_user_api_commands_sorted.putAll(regular_user_api_commands); try { //Create object writer XStream xs = new XStream(); xs.alias("command", Command.class); xs.alias("arg", Argument.class); String xmlDocDir = dirName + "/xmldoc"; String rootAdminDirName = xmlDocDir + "/root_admin"; String domainAdminDirName = xmlDocDir + "/domain_admin"; String regularUserDirName = xmlDocDir + "/regular_user"; (new File(rootAdminDirName)).mkdirs(); (new File(domainAdminDirName)).mkdirs(); (new File(regularUserDirName)).mkdirs(); ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(dirName + "/commands.xml"), "commands"); ObjectOutputStream rootAdmin = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "rootAdminSummary.xml"), "commands"); ObjectOutputStream rootAdminSorted = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "rootAdminSummarySorted.xml"), "commands"); ObjectOutputStream domainAdmin = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "domainAdminSummary.xml"), "commands"); ObjectOutputStream outDomainAdminSorted = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "domainAdminSummarySorted.xml"), "commands"); ObjectOutputStream regularUser = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/regularUserSummary.xml"), "commands"); ObjectOutputStream regularUserSorted = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/regularUserSummarySorted.xml"), "commands"); //Write commands in the order they are represented in commands.properties.in file Iterator it = all_api_commands.keySet().iterator(); while (it.hasNext()) { String key = (String)it.next(); //Write admin commands if (key.equals("login")) { writeLoginCommand(out); writeLoginCommand(rootAdmin); writeLoginCommand(domainAdmin); writeLoginCommand(regularUser); ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "login" + ".xml"), "command"); writeLoginCommand(singleRootAdminCommandOs); singleRootAdminCommandOs.close(); ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "login" + ".xml"), "command"); writeLoginCommand(singleDomainAdminCommandOs); singleDomainAdminCommandOs.close(); ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + "login" + ".xml"), "command"); writeLoginCommand(singleRegularUserCommandOs); singleRegularUserCommandOs.close(); } else if (key.equals("logout")) { writeLogoutCommand(out); writeLogoutCommand(rootAdmin); writeLogoutCommand(domainAdmin); writeLogoutCommand(regularUser); ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "logout" + ".xml"), "command"); writeLogoutCommand(singleRootAdminCommandOs); singleRootAdminCommandOs.close(); ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "logout" + ".xml"), "command"); writeLogoutCommand(singleDomainAdminCommandOs); singleDomainAdminCommandOs.close(); ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + "logout" + ".xml"), "command"); writeLogoutCommand(singleRegularUserCommandOs); singleRegularUserCommandOs.close(); } else { writeCommand(out, key); writeCommand(rootAdmin, key); //Write single commands to separate xml files if (!key.equals("login")) { ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + key + ".xml"), "command"); writeCommand(singleRootAdminCommandOs, key); singleRootAdminCommandOs.close(); } if (domain_admin_api_commands.containsKey(key)){ writeCommand(domainAdmin, key); ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + key + ".xml"), "command"); writeCommand(singleDomainAdminCommandOs, key); singleDomainAdminCommandOs.close(); } if (regular_user_api_commands.containsKey(key)){ writeCommand(regularUser, key); ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + key + ".xml"), "command"); writeCommand(singleRegularUserCommandOs, key); singleRegularUserCommandOs.close(); } } } //Write sorted commands it = all_api_commands_sorted.keySet().iterator(); while (it.hasNext()) { String key = (String)it.next(); if (key.equals("login")) { writeLoginCommand(rootAdminSorted); writeLoginCommand(outDomainAdminSorted); writeLoginCommand(regularUserSorted); } else if (key.equals("logout")) { writeLogoutCommand(rootAdminSorted); writeLogoutCommand(outDomainAdminSorted); writeLogoutCommand(regularUserSorted); } else { writeCommand(rootAdminSorted, key); if (domain_admin_api_commands.containsKey(key)){ writeCommand(outDomainAdminSorted, key); } if (regular_user_api_commands.containsKey(key)){ writeCommand(regularUserSorted, key); } } } out.close(); rootAdmin.close(); rootAdminSorted.close(); domainAdmin.close(); outDomainAdminSorted.close(); regularUser.close(); regularUserSorted.close(); //write alerttypes to xml writeAlertTypes(xmlDocDir); //gzip directory with xml doc //zipDir(dirName + "xmldoc.zip", xmlDocDir); //Delete directory //deleteDir(new File(xmlDocDir)); } catch (Exception ex) { ex.printStackTrace(); System.exit(2); } } private static void writeCommand(ObjectOutputStream out, String command) throws ClassNotFoundException, IOException{ Class clas = Class.forName(all_api_commands.get(command)); ArrayList request = new ArrayList(); ArrayList response = new ArrayList(); //Create a new command, set name and description Command apiCommand = new Command(); apiCommand.setName(command); Implementation impl = (Implementation)clas.getAnnotation(Implementation.class); if (impl == null) impl = (Implementation)clas.getSuperclass().getAnnotation(Implementation.class); String commandDescription = impl.description(); if (commandDescription != null) apiCommand.setDescription(commandDescription); else System.out.println("Command " + apiCommand.getName() + " misses description"); //Set request parameters Field[] fields = clas.getDeclaredFields(); //Get fields from superclass Class superClass = clas.getSuperclass(); String superName = superClass.getName(); if (!superName.equals(BaseCmd.class.getName()) && !superName.equals(BaseAsyncCmd.class.getName()) && !superName.equals(BaseAsyncCreateCmd.class.getName())) { Field[] superClassFields = superClass.getDeclaredFields(); if (superClassFields != null && !superClass.getName().equals(BaseListCmd.class.getName())) { Field[] tmpFields = new Field[fields.length + superClassFields.length]; System.arraycopy(fields, 0, tmpFields, 0, fields.length); System.arraycopy(superClassFields, 0, tmpFields, fields.length, superClassFields.length); fields = tmpFields; } superClass = superClass.getSuperclass(); } request = setRequestFields(fields); //Get response parameters Class responseClas = impl.responseObject(); Field[] responseFields = responseClas.getDeclaredFields(); response = setResponseFields(responseFields); apiCommand.setRequest(request); apiCommand.setResponse(response); out.writeObject(apiCommand); } private static void writeLoginCommand(ObjectOutputStream out) throws ClassNotFoundException, IOException{ ArrayList request = new ArrayList(); ArrayList response = new ArrayList(); //Create a new command, set name and description Command apiCommand = new Command(); apiCommand.setName("login"); apiCommand.setDescription("Logs a user into the CloudStack. A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired."); //Generate request request.add(new Argument("username", "Username", true)); request.add(new Argument("password", "Password", true)); request.add(new Argument("domainid", "domain ID that the user belongs to. If no domain ID is passed in, the ROOT domain is assumed.", false)); apiCommand.setRequest(request); //Generate response response.add(new Argument("username", "Username")); response.add(new Argument("userid", "User id")); response.add(new Argument("password", "Password")); response.add(new Argument("domainid", "domain ID that the user belongs to. If no domain ID is passed in, the ROOT domain is assumed.")); response.add(new Argument("timeout", "the time period before the session has expired")); response.add(new Argument("account", "the account name the user belongs to")); response.add(new Argument("firstname", "first name of the user")); response.add(new Argument("lastname", "last name of the user")); response.add(new Argument("type", "the account type (admin, domain-admin, read-only-admin, user)")); response.add(new Argument("timezone", "user time zone")); response.add(new Argument("timezoneoffset", "user time zone offset from UTC 00:00")); response.add(new Argument("sessionkey", "Session key that can be passed in subsequent Query command calls")); apiCommand.setResponse(response); out.writeObject(apiCommand); } private static void writeLogoutCommand(ObjectOutputStream out) throws ClassNotFoundException, IOException{ ArrayList request = new ArrayList(); ArrayList response = new ArrayList(); //Create a new command, set name and description Command apiCommand = new Command(); apiCommand.setName("logout"); apiCommand.setDescription("Logs out the user"); //Generate request - no request parameters apiCommand.setRequest(request); //Generate response response.add(new Argument("success", "success if the logout action succeeded")); apiCommand.setResponse(response); out.writeObject(apiCommand); } private static ArrayList setRequestFields(Field[] fields) { ArrayList arguments = new ArrayList(); ArrayList requiredArguments = new ArrayList(); ArrayList optionalArguments = new ArrayList(); Argument id = null; for (Field f : fields) { Parameter parameterAnnotation = f.getAnnotation(Parameter.class); if (parameterAnnotation != null && parameterAnnotation.expose()) { Argument reqArg = new Argument(parameterAnnotation.name()); reqArg.setRequired(parameterAnnotation.required()); if (!parameterAnnotation.description().isEmpty()) { reqArg.setDescription(parameterAnnotation.description()); } if (reqArg.isRequired() == true) { if (parameterAnnotation.name().equals("id")) { id = reqArg; } else { requiredArguments.add(reqArg); } } else { optionalArguments.add(reqArg); } } } Collections.sort(requiredArguments); Collections.sort(optionalArguments); //sort required and optional arguments here if (id != null) { arguments.add(id); } arguments.addAll(requiredArguments); arguments.addAll(optionalArguments); return arguments; } private static ArrayList setResponseFields(Field[] responseFields) { ArrayList arguments = new ArrayList(); ArrayList sortedChildlessArguments = new ArrayList(); ArrayList sortedArguments = new ArrayList(); Argument id = null; for (Field responseField : responseFields) { SerializedName nameAnnotation = responseField.getAnnotation(SerializedName.class); Param paramAnnotation = responseField.getAnnotation(Param.class); Argument respArg = new Argument(nameAnnotation.value()); boolean toExpose = true; boolean hasChildren = false; if (paramAnnotation != null) { String description = paramAnnotation.description(); Class fieldClass = paramAnnotation.responseObject(); toExpose = paramAnnotation.expose(); if (description != null && !description.isEmpty()) { respArg.setDescription(description); } if (fieldClass != null) { Class superClass = fieldClass.getSuperclass(); if (superClass != null) { String superName = superClass.getName(); if (superName.equals(BaseResponse.class.getName())) { ArrayList fieldArguments = new ArrayList(); Field[] fields = fieldClass.getDeclaredFields(); fieldArguments = setResponseFields(fields); respArg.setArguments(fieldArguments); hasChildren = true; } } } } if (toExpose) { if (nameAnnotation.value().equals("id")) { id = respArg; } else { if (hasChildren) { respArg.setName(nameAnnotation.value() + "(*)"); sortedArguments.add(respArg); } else { sortedChildlessArguments.add(respArg); } } } } Collections.sort(sortedArguments); Collections.sort(sortedChildlessArguments); if (id != null) { arguments.add(id); } arguments.addAll(sortedChildlessArguments); arguments.addAll(sortedArguments); return arguments; } private static void zipDir(String zipFileName, String dir) throws Exception { File dirObj = new File(dir); ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName)); addDir(dirObj, out); out.close(); } static void addDir(File dirObj, ZipOutputStream out) throws IOException { File[] files = dirObj.listFiles(); byte[] tmpBuf = new byte[1024]; String pathToDir = dirName; for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { addDir(files[i], out); continue; } FileInputStream in = new FileInputStream(files[i].getPath()); out.putNextEntry(new ZipEntry(files[i].getPath().substring(pathToDir.length()))); int len; while ((len = in.read(tmpBuf)) > 0) { out.write(tmpBuf, 0, len); } out.closeEntry(); in.close(); } } private static void deleteDir(File dir) { if (dir.isDirectory()) { String[] children = dir.list(); for (int i=0; i keys = new LinkedList(); public Enumeration keys() { return Collections.enumeration(keys); } public Object put(Object key, Object value) { //System.out.println("Adding key" + key); keys.add(key); return super.put(key, value); } } }