Skip to content

Commit 5d67c98

Browse files
author
Prasanna Santhanam
committed
marvin+apidiscovery: Extend API discovery plugin
API discovery plugin will return embedded entities for marvin to discovery and generate it's API classes. Signed-off-by: Prasanna Santhanam <tsp@apache.org>
1 parent d4dc264 commit 5d67c98

10 files changed

Lines changed: 295 additions & 110 deletions

File tree

client/pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,6 @@
9090
<artifactId>cloud-plugin-hypervisor-baremetal</artifactId>
9191
<version>${project.version}</version>
9292
</dependency>
93-
<dependency>
94-
<groupId>org.apache.cloudstack</groupId>
95-
<artifactId>cloud-plugin-hypervisor-ucs</artifactId>
96-
<version>${project.version}</version>
97-
</dependency>
9893
<dependency>
9994
<groupId>org.apache.cloudstack</groupId>
10095
<artifactId>cloud-plugin-hypervisor-ovm</artifactId>

plugins/api/discovery/src/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public class ApiDiscoveryResponse extends BaseResponse {
4747
@SerializedName(ApiConstants.RESPONSE) @Param(description="api response fields", responseObject = ApiResponseResponse.class)
4848
private Set<ApiResponseResponse> apiResponse;
4949

50+
@SerializedName(ApiConstants.TYPE) @Param(description="response field type")
51+
private String type;
52+
5053
public ApiDiscoveryResponse(){
5154
params = new HashSet<ApiParameterResponse>();
5255
apiResponse = new HashSet<ApiResponseResponse>();
@@ -81,6 +84,7 @@ public void setAsync(Boolean isAsync) {
8184
this.isAsync = isAsync;
8285
}
8386

87+
8488
public boolean getAsync() {
8589
return isAsync;
8690
}

plugins/api/discovery/src/org/apache/cloudstack/api/response/ApiResponseResponse.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.response;
1818

19-
import org.apache.cloudstack.api.ApiConstants;
2019
import com.cloud.serializer.Param;
2120
import com.google.gson.annotations.SerializedName;
21+
import org.apache.cloudstack.api.ApiConstants;
2222
import org.apache.cloudstack.api.BaseResponse;
2323

24+
import java.util.HashSet;
25+
import java.util.Set;
26+
2427
public class ApiResponseResponse extends BaseResponse {
2528
@SerializedName(ApiConstants.NAME) @Param(description="the name of the api response field")
2629
private String name;
@@ -31,6 +34,9 @@ public class ApiResponseResponse extends BaseResponse {
3134
@SerializedName(ApiConstants.TYPE) @Param(description="response field type")
3235
private String type;
3336

37+
@SerializedName(ApiConstants.RESPONSE) @Param(description="api response fields")
38+
private Set<ApiResponseResponse> apiResponse;
39+
3440
public void setName(String name) {
3541
this.name = name;
3642
}
@@ -42,4 +48,11 @@ public void setDescription(String description) {
4248
public void setType(String type) {
4349
this.type = type;
4450
}
51+
52+
public void addApiResponse(ApiResponseResponse childApiResponse) {
53+
if(this.apiResponse == null) {
54+
this.apiResponse = new HashSet<ApiResponseResponse>();
55+
}
56+
this.apiResponse.add(childApiResponse);
57+
}
4558
}

plugins/api/discovery/src/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java

Lines changed: 96 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,14 @@
1616
// under the License.
1717
package org.apache.cloudstack.discovery;
1818

19-
import java.lang.reflect.Field;
20-
import java.util.ArrayList;
21-
import java.util.HashMap;
22-
import java.util.HashSet;
23-
import java.util.List;
24-
import java.util.Map;
25-
import java.util.Set;
26-
27-
import javax.annotation.PostConstruct;
28-
import javax.ejb.Local;
29-
import javax.inject.Inject;
30-
19+
import com.cloud.serializer.Param;
20+
import com.cloud.user.User;
21+
import com.cloud.utils.ReflectUtil;
22+
import com.cloud.utils.StringUtils;
23+
import com.cloud.utils.component.PluggableService;
24+
import com.google.gson.annotations.SerializedName;
3125
import org.apache.cloudstack.acl.APIChecker;
32-
import org.apache.cloudstack.api.APICommand;
33-
import org.apache.cloudstack.api.BaseAsyncCmd;
34-
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
35-
import org.apache.cloudstack.api.BaseCmd;
36-
import org.apache.cloudstack.api.BaseResponse;
37-
import org.apache.cloudstack.api.Parameter;
26+
import org.apache.cloudstack.api.*;
3827
import org.apache.cloudstack.api.command.user.discovery.ListApisCmd;
3928
import org.apache.cloudstack.api.response.ApiDiscoveryResponse;
4029
import org.apache.cloudstack.api.response.ApiParameterResponse;
@@ -43,12 +32,11 @@
4332
import org.apache.log4j.Logger;
4433
import org.springframework.stereotype.Component;
4534

46-
import com.cloud.serializer.Param;
47-
import com.cloud.user.User;
48-
import com.cloud.utils.ReflectUtil;
49-
import com.cloud.utils.StringUtils;
50-
import com.cloud.utils.component.PluggableService;
51-
import com.google.gson.annotations.SerializedName;
35+
import javax.annotation.PostConstruct;
36+
import javax.ejb.Local;
37+
import javax.inject.Inject;
38+
import java.lang.reflect.Field;
39+
import java.util.*;
5240

5341
@Component
5442
@Local(value = ApiDiscoveryService.class)
@@ -69,9 +57,9 @@ void init() {
6957
long startTime = System.nanoTime();
7058
s_apiNameDiscoveryResponseMap = new HashMap<String, ApiDiscoveryResponse>();
7159
Set<Class<?>> cmdClasses = new HashSet<Class<?>>();
72-
for(PluggableService service: _services) {
60+
for(PluggableService service: _services) {
7361
s_logger.debug(String.format("getting api commands of service: %s", service.getClass().getName()));
74-
cmdClasses.addAll(service.getCommands());
62+
cmdClasses.addAll(service.getCommands());
7563
}
7664
cmdClasses.addAll(this.getCommands());
7765
cacheResponseMap(cmdClasses);
@@ -80,72 +68,39 @@ void init() {
8068
}
8169
}
8270

83-
protected void cacheResponseMap(Set<Class<?>> cmdClasses) {
71+
protected Map<String, List<String>> cacheResponseMap(Set<Class<?>> cmdClasses) {
8472
Map<String, List<String>> responseApiNameListMap = new HashMap<String, List<String>>();
8573

8674
for(Class<?> cmdClass: cmdClasses) {
8775
APICommand apiCmdAnnotation = cmdClass.getAnnotation(APICommand.class);
88-
if (apiCmdAnnotation == null)
76+
if (apiCmdAnnotation == null) {
8977
apiCmdAnnotation = cmdClass.getSuperclass().getAnnotation(APICommand.class);
78+
}
9079
if (apiCmdAnnotation == null
9180
|| !apiCmdAnnotation.includeInApiDoc()
92-
|| apiCmdAnnotation.name().isEmpty())
81+
|| apiCmdAnnotation.name().isEmpty()) {
9382
continue;
83+
}
9484

9585
String apiName = apiCmdAnnotation.name();
86+
ApiDiscoveryResponse response = getCmdRequestMap(cmdClass, apiCmdAnnotation);
87+
9688
String responseName = apiCmdAnnotation.responseObject().getName();
9789
if (!responseName.contains("SuccessResponse")) {
98-
if (!responseApiNameListMap.containsKey(responseName))
90+
if (!responseApiNameListMap.containsKey(responseName)) {
9991
responseApiNameListMap.put(responseName, new ArrayList<String>());
92+
}
10093
responseApiNameListMap.get(responseName).add(apiName);
10194
}
102-
ApiDiscoveryResponse response = new ApiDiscoveryResponse();
103-
response.setName(apiName);
104-
response.setDescription(apiCmdAnnotation.description());
105-
if (!apiCmdAnnotation.since().isEmpty())
106-
response.setSince(apiCmdAnnotation.since());
10795
response.setRelated(responseName);
10896

97+
10998
Field[] responseFields = apiCmdAnnotation.responseObject().getDeclaredFields();
11099
for(Field responseField: responseFields) {
111-
SerializedName serializedName = responseField.getAnnotation(SerializedName.class);
112-
if(serializedName != null) {
113-
ApiResponseResponse responseResponse = new ApiResponseResponse();
114-
responseResponse.setName(serializedName.value());
115-
Param param = responseField.getAnnotation(Param.class);
116-
if (param != null)
117-
responseResponse.setDescription(param.description());
118-
responseResponse.setType(responseField.getType().getSimpleName().toLowerCase());
119-
response.addApiResponse(responseResponse);
120-
}
100+
ApiResponseResponse responseResponse = getFieldResponseMap(responseField);
101+
response.addApiResponse(responseResponse);
121102
}
122103

123-
Set<Field> fields = ReflectUtil.getAllFieldsForClass(cmdClass,
124-
new Class<?>[]{BaseCmd.class, BaseAsyncCmd.class, BaseAsyncCreateCmd.class});
125-
126-
boolean isAsync = ReflectUtil.isCmdClassAsync(cmdClass,
127-
new Class<?>[] {BaseAsyncCmd.class, BaseAsyncCreateCmd.class});
128-
129-
response.setAsync(isAsync);
130-
131-
for(Field field: fields) {
132-
Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
133-
if (parameterAnnotation != null
134-
&& parameterAnnotation.expose()
135-
&& parameterAnnotation.includeInApiDoc()) {
136-
137-
ApiParameterResponse paramResponse = new ApiParameterResponse();
138-
paramResponse.setName(parameterAnnotation.name());
139-
paramResponse.setDescription(parameterAnnotation.description());
140-
paramResponse.setType(parameterAnnotation.type().toString().toLowerCase());
141-
paramResponse.setLength(parameterAnnotation.length());
142-
paramResponse.setRequired(parameterAnnotation.required());
143-
if (!parameterAnnotation.since().isEmpty())
144-
paramResponse.setSince(parameterAnnotation.since());
145-
paramResponse.setRelated(parameterAnnotation.entityType()[0].getName());
146-
response.addParam(paramResponse);
147-
}
148-
}
149104
response.setObjectName("api");
150105
s_apiNameDiscoveryResponseMap.put(apiName, response);
151106
}
@@ -173,6 +128,76 @@ protected void cacheResponseMap(Set<Class<?>> cmdClasses) {
173128
}
174129
s_apiNameDiscoveryResponseMap.put(apiName, response);
175130
}
131+
return responseApiNameListMap;
132+
}
133+
134+
private ApiResponseResponse getFieldResponseMap(Field responseField) {
135+
ApiResponseResponse responseResponse = new ApiResponseResponse();
136+
SerializedName serializedName = responseField.getAnnotation(SerializedName.class);
137+
Param param = responseField.getAnnotation(Param.class);
138+
if (serializedName != null && param != null) {
139+
responseResponse.setName(serializedName.value());
140+
responseResponse.setDescription(param.description());
141+
responseResponse.setType(responseField.getType().getSimpleName().toLowerCase());
142+
//If response is not of primitive type - we have a nested entity
143+
Class fieldClass = param.responseObject();
144+
if (fieldClass != null) {
145+
Class<?> superClass = fieldClass.getSuperclass();
146+
if (superClass != null) {
147+
String superName = superClass.getName();
148+
if (superName.equals(BaseResponse.class.getName())) {
149+
Field[] fields = fieldClass.getDeclaredFields();
150+
for (Field field : fields) {
151+
ApiResponseResponse innerResponse = getFieldResponseMap(field);
152+
if (innerResponse != null) {
153+
responseResponse.addApiResponse(innerResponse);
154+
}
155+
}
156+
}
157+
}
158+
}
159+
}
160+
return responseResponse;
161+
}
162+
163+
private ApiDiscoveryResponse getCmdRequestMap(Class<?> cmdClass, APICommand apiCmdAnnotation) {
164+
String apiName = apiCmdAnnotation.name();
165+
ApiDiscoveryResponse response = new ApiDiscoveryResponse();
166+
response.setName(apiName);
167+
response.setDescription(apiCmdAnnotation.description());
168+
if (!apiCmdAnnotation.since().isEmpty()) {
169+
response.setSince(apiCmdAnnotation.since());
170+
}
171+
172+
173+
Set<Field> fields = ReflectUtil.getAllFieldsForClass(cmdClass,
174+
new Class<?>[]{BaseCmd.class, BaseAsyncCmd.class, BaseAsyncCreateCmd.class});
175+
176+
boolean isAsync = ReflectUtil.isCmdClassAsync(cmdClass,
177+
new Class<?>[]{BaseAsyncCmd.class, BaseAsyncCreateCmd.class});
178+
179+
response.setAsync(isAsync);
180+
181+
for(Field field: fields) {
182+
Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
183+
if (parameterAnnotation != null
184+
&& parameterAnnotation.expose()
185+
&& parameterAnnotation.includeInApiDoc()) {
186+
187+
ApiParameterResponse paramResponse = new ApiParameterResponse();
188+
paramResponse.setName(parameterAnnotation.name());
189+
paramResponse.setDescription(parameterAnnotation.description());
190+
paramResponse.setType(parameterAnnotation.type().toString().toLowerCase());
191+
paramResponse.setLength(parameterAnnotation.length());
192+
paramResponse.setRequired(parameterAnnotation.required());
193+
if (!parameterAnnotation.since().isEmpty()) {
194+
paramResponse.setSince(parameterAnnotation.since());
195+
}
196+
paramResponse.setRelated(parameterAnnotation.entityType()[0].getName());
197+
response.addParam(paramResponse);
198+
}
199+
}
200+
return response;
176201
}
177202

178203
@Override

server/src/com/cloud/api/response/ApiResponseSerializer.java

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,25 @@
1616
// under the License.
1717
package com.cloud.api.response;
1818

19-
import java.lang.reflect.Field;
20-
import java.lang.reflect.Method;
21-
import java.lang.reflect.Modifier;
22-
import java.util.ArrayList;
23-
import java.util.Arrays;
24-
import java.util.Collection;
25-
import java.util.Date;
26-
import java.util.List;
27-
import java.util.regex.Matcher;
28-
import java.util.regex.Pattern;
29-
30-
import org.apache.cloudstack.api.response.ListResponse;
31-
import org.apache.cloudstack.api.response.*;
32-
import org.apache.log4j.Logger;
33-
34-
import org.apache.cloudstack.api.ApiConstants;
3519
import com.cloud.api.ApiDBUtils;
3620
import com.cloud.api.ApiResponseGsonHelper;
3721
import com.cloud.api.ApiServer;
38-
import org.apache.cloudstack.api.BaseCmd;
39-
import org.apache.cloudstack.api.ResponseObject;
4022
import com.cloud.utils.encoding.URLEncoder;
4123
import com.cloud.utils.exception.CloudRuntimeException;
42-
import com.cloud.uuididentity.dao.IdentityDao;
43-
import com.cloud.uuididentity.dao.IdentityDaoImpl;
4424
import com.google.gson.Gson;
4525
import com.google.gson.annotations.SerializedName;
26+
import org.apache.cloudstack.api.ApiConstants;
27+
import org.apache.cloudstack.api.BaseCmd;
28+
import org.apache.cloudstack.api.ResponseObject;
29+
import org.apache.cloudstack.api.response.*;
30+
import org.apache.log4j.Logger;
31+
32+
import java.lang.reflect.Field;
33+
import java.lang.reflect.Method;
34+
import java.lang.reflect.Modifier;
35+
import java.util.*;
36+
import java.util.regex.Matcher;
37+
import java.util.regex.Pattern;
4638

4739
public class ApiResponseSerializer {
4840
private static final Logger s_logger = Logger.getLogger(ApiResponseSerializer.class.getName());
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package com.cloud.api.response;
21+
22+
import com.cloud.serializer.Param;
23+
import com.google.gson.ExclusionStrategy;
24+
import com.google.gson.FieldAttributes;
25+
26+
public class EmptyFieldExclusionStrategy implements ExclusionStrategy {
27+
28+
@Override
29+
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
30+
if (fieldAttributes.getAnnotation(Param.class) != null) {
31+
return true;
32+
}
33+
return false;
34+
}
35+
36+
@Override
37+
public boolean shouldSkipClass(Class<?> aClass) {
38+
return false;
39+
}
40+
}

0 commit comments

Comments
 (0)