| // 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 com.cloud.api.doc; |
| |
| import com.cloud.alert.AlertManager; |
| import com.cloud.serializer.Param; |
| import com.cloud.utils.IteratorUtil; |
| import com.cloud.utils.ReflectUtil; |
| import com.google.gson.annotations.SerializedName; |
| import com.thoughtworks.xstream.XStream; |
| import org.apache.cloudstack.api.APICommand; |
| import org.apache.cloudstack.api.BaseAsyncCmd; |
| import org.apache.cloudstack.api.BaseAsyncCreateCmd; |
| import org.apache.cloudstack.api.BaseCmd; |
| import org.apache.cloudstack.api.BaseResponse; |
| import org.apache.cloudstack.api.Parameter; |
| import org.apache.cloudstack.api.response.AsyncJobResponse; |
| import org.apache.cloudstack.api.response.HostResponse; |
| import org.apache.cloudstack.api.response.IPAddressResponse; |
| import org.apache.cloudstack.api.response.SecurityGroupResponse; |
| import org.apache.cloudstack.api.response.SnapshotResponse; |
| import org.apache.cloudstack.api.response.StoragePoolResponse; |
| import org.apache.cloudstack.api.response.TemplateResponse; |
| import org.apache.cloudstack.api.response.UserVmResponse; |
| import org.apache.cloudstack.api.response.VolumeResponse; |
| import org.apache.log4j.Logger; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| 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.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| |
| public class ApiXmlDocWriter { |
| public static final Logger s_logger = Logger.getLogger(ApiXmlDocWriter.class.getName()); |
| |
| private static String s_dirName = ""; |
| private static Map<String, Class<?>> s_apiNameCmdClassMap = new HashMap<String, Class<?>>(); |
| private static LinkedHashMap<Object, String> s_allApiCommands = new LinkedHashMap<Object, String>(); |
| private static TreeMap<Object, String> s_allApiCommandsSorted = new TreeMap<Object, String>(); |
| private static final List<String> AsyncResponses = setAsyncResponses(); |
| |
| private static List<String> setAsyncResponses() { |
| List<String> asyncResponses = new ArrayList<String>(); |
| asyncResponses.add(TemplateResponse.class.getName()); |
| asyncResponses.add(VolumeResponse.class.getName()); |
| //asyncResponses.add(LoadBalancerResponse.class.getName()); |
| asyncResponses.add(HostResponse.class.getName()); |
| asyncResponses.add(IPAddressResponse.class.getName()); |
| asyncResponses.add(StoragePoolResponse.class.getName()); |
| asyncResponses.add(UserVmResponse.class.getName()); |
| asyncResponses.add(SecurityGroupResponse.class.getName()); |
| //asyncResponses.add(ExternalLoadBalancerResponse.class.getName()); |
| asyncResponses.add(SnapshotResponse.class.getName()); |
| |
| return asyncResponses; |
| } |
| |
| public static void main(String[] args) { |
| Set<Class<?>> cmdClasses = ReflectUtil.getClassesWithAnnotation(APICommand.class, new String[] {"org.apache.cloudstack.api", "com.cloud.api", |
| "com.cloud.api.commands", "com.globo.globodns.cloudstack.api", "org.apache.cloudstack.network.opendaylight.api", |
| "org.apache.cloudstack.api.command.admin.zone", "org.apache.cloudstack.network.contrail.api.command"}); |
| |
| for (Class<?> cmdClass : cmdClasses) { |
| if(cmdClass.getAnnotation(APICommand.class)==null){ |
| System.out.println("Warning, API Cmd class " + cmdClass.getName() + " has no APICommand annotation "); |
| continue; |
| } |
| String apiName = cmdClass.getAnnotation(APICommand.class).name(); |
| if (s_apiNameCmdClassMap.containsKey(apiName)) { |
| // handle API cmd separation into admin cmd and user cmd with the common api name |
| Class<?> curCmd = s_apiNameCmdClassMap.get(apiName); |
| if (curCmd.isAssignableFrom(cmdClass)) { |
| // api_cmd map always keep the admin cmd class to get full response and parameters |
| s_apiNameCmdClassMap.put(apiName, cmdClass); |
| } else if (cmdClass.isAssignableFrom(curCmd)) { |
| // just skip this one without warning |
| continue; |
| } else { |
| System.out.println("Warning, API Cmd class " + cmdClass.getName() + " has non-unique apiname " + apiName); |
| continue; |
| } |
| } else { |
| s_apiNameCmdClassMap.put(apiName, cmdClass); |
| } |
| } |
| System.out.printf("Scanned and found %d APIs\n", s_apiNameCmdClassMap.size()); |
| List<String> argsList = Arrays.asList(args); |
| Iterator<String> iter = argsList.iterator(); |
| while (iter.hasNext()) { |
| String arg = iter.next(); |
| if (arg.equals("-d")) { |
| s_dirName = iter.next(); |
| } |
| } |
| |
| for (Map.Entry<String, Class<?>> entry: s_apiNameCmdClassMap.entrySet()) { |
| Class<?> cls = entry.getValue(); |
| s_allApiCommands.put(entry.getKey(), cls.getName()); |
| } |
| |
| s_allApiCommandsSorted.putAll(s_allApiCommands); |
| |
| try { |
| // Create object writer |
| XStream xs = new XStream(); |
| xs.alias("command", Command.class); |
| xs.alias("arg", Argument.class); |
| String xmlDocDir = s_dirName + "/xmldoc"; |
| String rootAdminDirName = xmlDocDir + "/apis"; |
| (new File(rootAdminDirName)).mkdirs(); |
| |
| ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(s_dirName + "/commands.xml"), "commands"); |
| ObjectOutputStream rootAdmin = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "apiSummary.xml"), "commands"); |
| ObjectOutputStream rootAdminSorted = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "apiSummarySorted.xml"), "commands"); |
| |
| Iterator<?> it = s_allApiCommands.keySet().iterator(); |
| while (it.hasNext()) { |
| String key = (String)it.next(); |
| // Write admin commands |
| writeCommand(out, key); |
| writeCommand(rootAdmin, key); |
| // Write single commands to separate xml files |
| ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + key + ".xml"), "command"); |
| writeCommand(singleRootAdminCommandOs, key); |
| singleRootAdminCommandOs.close(); |
| } |
| |
| // Write sorted commands |
| it = s_allApiCommandsSorted.keySet().iterator(); |
| while (it.hasNext()) { |
| String key = (String)it.next(); |
| writeCommand(rootAdminSorted, key); |
| } |
| |
| out.close(); |
| rootAdmin.close(); |
| rootAdminSorted.close(); |
| |
| // write alerttypes to xml |
| writeAlertTypes(xmlDocDir); |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| System.exit(2); |
| } |
| } |
| |
| private static void writeCommand(ObjectOutputStream out, String command) throws ClassNotFoundException, IOException { |
| Class<?> clas = Class.forName(s_allApiCommands.get(command)); |
| ArrayList<Argument> request = new ArrayList<Argument>(); |
| ArrayList<Argument> response = new ArrayList<Argument>(); |
| |
| // Create a new command, set name/description/usage |
| Command apiCommand = new Command(); |
| apiCommand.setName(command); |
| |
| APICommand impl = clas.getAnnotation(APICommand.class); |
| if (impl == null) { |
| impl = clas.getSuperclass().getAnnotation(APICommand.class); |
| } |
| |
| if (impl == null) { |
| throw new IllegalStateException(String.format("An %1$s annotation is required for class %2$s.", APICommand.class.getCanonicalName(), clas.getCanonicalName())); |
| } |
| |
| if (impl.includeInApiDoc()) { |
| String commandDescription = impl.description(); |
| if (commandDescription != null && !commandDescription.isEmpty()) { |
| apiCommand.setDescription(commandDescription); |
| } else { |
| System.out.println("Command " + apiCommand.getName() + " misses description"); |
| } |
| |
| String commandUsage = impl.usage(); |
| if (commandUsage != null && !commandUsage.isEmpty()) { |
| apiCommand.setUsage(commandUsage); |
| } |
| |
| //Set version when the API is added |
| if (!impl.since().isEmpty()) { |
| apiCommand.setSinceVersion(impl.since()); |
| } |
| |
| boolean isAsync = ReflectUtil.isCmdClassAsync(clas, new Class<?>[] {BaseAsyncCmd.class, BaseAsyncCreateCmd.class}); |
| |
| apiCommand.setAsync(isAsync); |
| |
| Set<Field> fields = ReflectUtil.getAllFieldsForClass(clas, new Class<?>[] {BaseCmd.class, BaseAsyncCmd.class, BaseAsyncCreateCmd.class}); |
| |
| request = setRequestFields(fields); |
| |
| // Get response parameters |
| Class<?> responseClas = impl.responseObject(); |
| Field[] responseFields = responseClas.getDeclaredFields(); |
| response = setResponseFields(responseFields, responseClas); |
| |
| apiCommand.setRequest(request); |
| apiCommand.setResponse(response); |
| |
| out.writeObject(apiCommand); |
| } else { |
| s_logger.debug("Command " + command + " is not exposed in api doc"); |
| } |
| } |
| |
| private static ArrayList<Argument> setRequestFields(Set<Field> fields) { |
| ArrayList<Argument> arguments = new ArrayList<Argument>(); |
| Set<Argument> requiredArguments = new HashSet<Argument>(); |
| Set<Argument> optionalArguments = new HashSet<Argument>(); |
| Argument id = null; |
| for (Field f : fields) { |
| Parameter parameterAnnotation = f.getAnnotation(Parameter.class); |
| if (parameterAnnotation != null && parameterAnnotation.expose() && parameterAnnotation.includeInApiDoc()) { |
| Argument reqArg = new Argument(parameterAnnotation.name()); |
| reqArg.setRequired(parameterAnnotation.required()); |
| if (!parameterAnnotation.description().isEmpty()) { |
| reqArg.setDescription(parameterAnnotation.description()); |
| } |
| |
| if (parameterAnnotation.type() == BaseCmd.CommandType.LIST || parameterAnnotation.type() == BaseCmd.CommandType.MAP) { |
| reqArg.setType(parameterAnnotation.type().toString().toLowerCase()); |
| } |
| |
| reqArg.setDataType(parameterAnnotation.type().toString().toLowerCase()); |
| |
| if (!parameterAnnotation.since().isEmpty()) { |
| reqArg.setSinceVersion(parameterAnnotation.since()); |
| } |
| |
| if (reqArg.isRequired()) { |
| if (parameterAnnotation.name().equals("id")) { |
| id = reqArg; |
| } else { |
| requiredArguments.add(reqArg); |
| } |
| } else { |
| optionalArguments.add(reqArg); |
| } |
| } |
| } |
| |
| // sort required and optional arguments here |
| if (id != null) { |
| arguments.add(id); |
| } |
| arguments.addAll(IteratorUtil.asSortedList(requiredArguments)); |
| arguments.addAll(IteratorUtil.asSortedList(optionalArguments)); |
| |
| return arguments; |
| } |
| |
| private static ArrayList<Argument> setResponseFields(Field[] responseFields, Class<?> responseClas) { |
| ArrayList<Argument> arguments = new ArrayList<Argument>(); |
| ArrayList<Argument> sortedChildlessArguments = new ArrayList<Argument>(); |
| ArrayList<Argument> sortedArguments = new ArrayList<Argument>(); |
| |
| Argument id = null; |
| |
| for (Field responseField : responseFields) { |
| SerializedName nameAnnotation = responseField.getAnnotation(SerializedName.class); |
| if (nameAnnotation != null) { |
| Param paramAnnotation = responseField.getAnnotation(Param.class); |
| Argument respArg = new Argument(nameAnnotation.value()); |
| |
| boolean hasChildren = false; |
| if (paramAnnotation != null && paramAnnotation.includeInApiDoc()) { |
| String description = paramAnnotation.description(); |
| Class fieldClass = paramAnnotation.responseObject(); |
| if (description != null && !description.isEmpty()) { |
| respArg.setDescription(description); |
| } |
| |
| respArg.setDataType(responseField.getType().getSimpleName().toLowerCase()); |
| |
| if (!paramAnnotation.since().isEmpty()) { |
| respArg.setSinceVersion(paramAnnotation.since()); |
| } |
| |
| if (fieldClass != null) { |
| Class<?> superClass = fieldClass.getSuperclass(); |
| if (superClass != null) { |
| String superName = superClass.getName(); |
| if (superName.equals(BaseResponse.class.getName())) { |
| ArrayList<Argument> fieldArguments = new ArrayList<Argument>(); |
| Field[] fields = fieldClass.getDeclaredFields(); |
| fieldArguments = setResponseFields(fields, fieldClass); |
| respArg.setArguments(fieldArguments); |
| hasChildren = true; |
| } |
| } |
| } |
| } |
| |
| if (paramAnnotation != null && paramAnnotation.includeInApiDoc()) { |
| 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); |
| |
| if (responseClas.getName().equalsIgnoreCase(AsyncJobResponse.class.getName())) { |
| Argument jobIdArg = new Argument("jobid", "the ID of the async job"); |
| arguments.add(jobIdArg); |
| } else if (AsyncResponses.contains(responseClas.getName())) { |
| Argument jobIdArg = new Argument("jobid", "the ID of the latest async job acting on this object"); |
| Argument jobStatusArg = new Argument("jobstatus", "the current status of the latest async job acting on this object"); |
| arguments.add(jobIdArg); |
| arguments.add(jobStatusArg); |
| } |
| |
| 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 = s_dirName; |
| |
| for (int i = 0; i < files.length; i++) { |
| if (files[i].isDirectory()) { |
| addDir(files[i], out); |
| continue; |
| } |
| try(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(); |
| }catch(IOException ex) |
| { |
| s_logger.error("addDir:Exception:"+ ex.getMessage(),ex); |
| } |
| } |
| } |
| |
| private static void deleteDir(File dir) { |
| if (dir.isDirectory()) { |
| String[] children = dir.list(); |
| if (children != null) { |
| for (int i = 0; i < children.length; i++) { |
| deleteDir(new File(dir, children[i])); |
| } |
| } |
| } |
| dir.delete(); |
| } |
| |
| private static void writeAlertTypes(String dirName) { |
| XStream xs = new XStream(); |
| xs.alias("alert", Alert.class); |
| try(ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(dirName + "/alert_types.xml"), "alerts");) { |
| for (Field f : AlertManager.class.getFields()) { |
| if (f.getClass().isAssignableFrom(Number.class)) { |
| String name = f.getName().substring(11); |
| Alert alert = new Alert(name, f.getInt(null)); |
| out.writeObject(alert); |
| } |
| } |
| } catch (IOException e) { |
| s_logger.error("Failed to create output stream to write an alert types ", e); |
| } catch (IllegalAccessException e) { |
| s_logger.error("Failed to read alert fields ", e); |
| } |
| } |
| |
| private static class LinkedProperties extends Properties { |
| private final LinkedList<Object> keys = new LinkedList<Object>(); |
| |
| @Override |
| public Enumeration<Object> keys() { |
| return Collections.<Object> enumeration(keys); |
| } |
| |
| @Override |
| public Object put(Object key, Object value) { |
| // System.out.println("Adding key" + key); |
| keys.add(key); |
| return super.put(key, value); |
| } |
| } |
| } |