Skip to content

Commit 95abb6e

Browse files
committed
CLOUDSTACK-9361: Centrally handle API validations
Validate API arguments based on annotations. Introduces: - NotNullOrEmpty: for doing null and empty string checks - PositiveNumber: number > 0 (natural number) Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
1 parent 456680d commit 95abb6e

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.api;
19+
20+
public enum ApiArgValidator {
21+
NotNullOrEmpty, // does Strings.isNullOrEmpty check
22+
PositiveNumber, // does != null and > 0 check
23+
}

api/src/org/apache/cloudstack/api/Parameter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,6 @@
4949
String since() default "";
5050

5151
RoleType[] authorized() default {};
52+
53+
ApiArgValidator[] validations() default {};
5254
}

server/src/com/cloud/api/dispatch/ParamProcessWorker.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import javax.inject.Inject;
3636

37+
import com.google.common.base.Strings;
3738
import org.apache.log4j.Logger;
3839

3940
import org.apache.cloudstack.acl.ControlledEntity;
@@ -49,6 +50,7 @@
4950
import org.apache.cloudstack.api.InternalIdentity;
5051
import org.apache.cloudstack.api.Parameter;
5152
import org.apache.cloudstack.api.ServerApiException;
53+
import org.apache.cloudstack.api.ApiArgValidator;
5254
import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd;
5355
import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd;
5456
import org.apache.cloudstack.api.command.admin.usage.GetUsageRecordsCmd;
@@ -92,6 +94,55 @@ public void handle(final DispatchTask task) {
9294
processParameters(task.getCmd(), task.getParams());
9395
}
9496

97+
private void validateNonEmptyString(final Object param, final String argName) {
98+
if (param == null || Strings.isNullOrEmpty(param.toString())) {
99+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Empty or null value provided for API arg: %s", argName));
100+
}
101+
}
102+
103+
private void validateNaturalNumber(final Object param, final String argName) {
104+
Long value = null;
105+
if (param != null && param instanceof Long) {
106+
value = (Long) param;
107+
} else if (param != null) {
108+
value = Long.valueOf(param.toString());
109+
}
110+
if (value == null || value < 1L) {
111+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Invalid value provided for API arg: %s", argName));
112+
}
113+
}
114+
115+
private void validateField(final Object paramObj, final Parameter annotation) throws ServerApiException {
116+
if (annotation == null) {
117+
return;
118+
}
119+
final String argName = annotation.name();
120+
for (final ApiArgValidator validator : annotation.validations()) {
121+
if (validator == null) {
122+
continue;
123+
}
124+
switch (validator) {
125+
case NotNullOrEmpty:
126+
switch (annotation.type()) {
127+
case UUID:
128+
case STRING:
129+
validateNonEmptyString(paramObj, argName);
130+
break;
131+
}
132+
break;
133+
case PositiveNumber:
134+
switch (annotation.type()) {
135+
case SHORT:
136+
case INTEGER:
137+
case LONG:
138+
validateNaturalNumber(paramObj, argName);
139+
break;
140+
}
141+
break;
142+
}
143+
}
144+
}
145+
95146
@SuppressWarnings({"unchecked", "rawtypes"})
96147
public void processParameters(final BaseCmd cmd, final Map params) {
97148
final Map<Object, AccessType> entitiesToAccess = new HashMap<Object, AccessType>();
@@ -112,6 +163,7 @@ public void processParameters(final BaseCmd cmd, final Map params) {
112163

113164
// marshall the parameter into the correct type and set the field value
114165
try {
166+
validateField(paramObj, parameterAnnotation);
115167
setFieldValue(field, cmd, paramObj, parameterAnnotation);
116168
} catch (final IllegalArgumentException argEx) {
117169
if (s_logger.isDebugEnabled()) {
@@ -420,6 +472,7 @@ private Long translateUuidToInternalId(final String uuid, final Parameter annota
420472
for (final Class<?> entity : entities) {
421473
CallContext.current().putContextParameter(entity, internalId);
422474
}
475+
validateNaturalNumber(internalId, annotation.name());
423476
return internalId;
424477
}
425478
}
@@ -452,6 +505,7 @@ private Long translateUuidToInternalId(final String uuid, final Parameter annota
452505
throw new InvalidParameterValueException("Invalid parameter " + annotation.name() + " value=" + uuid +
453506
" due to incorrect long value format, or entity does not exist or due to incorrect parameter annotation for the field in api cmd class.");
454507
}
508+
validateNaturalNumber(internalId, annotation.name());
455509
return internalId;
456510
}
457511
}

0 commit comments

Comments
 (0)