Skip to content

Commit f6831ba

Browse files
committed
Add validations for string patterns and numeric ranges
1 parent 8671ee6 commit f6831ba

File tree

8 files changed

+141
-66
lines changed

8 files changed

+141
-66
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2018, Oracle Corporation and/or its affiliates. All rights reserved.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at
3+
// http://oss.oracle.com/licenses/upl.
4+
5+
package oracle.kubernetes.json;
6+
7+
import static java.lang.annotation.ElementType.FIELD;
8+
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.RetentionPolicy;
11+
import java.lang.annotation.Target;
12+
13+
/** Supplies an ECMA 262 regular expression that the field must match. */
14+
@Retention(RetentionPolicy.RUNTIME)
15+
@Target(FIELD)
16+
public @interface Pattern {
17+
String value();
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2018, Oracle Corporation and/or its affiliates. All rights reserved.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at
3+
// http://oss.oracle.com/licenses/upl.
4+
5+
package oracle.kubernetes.json;
6+
7+
import static java.lang.annotation.ElementType.FIELD;
8+
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.RetentionPolicy;
11+
import java.lang.annotation.Target;
12+
13+
/** Specifies minimum and/or maximum permitted values for the field. */
14+
@Retention(RetentionPolicy.RUNTIME)
15+
@Target(FIELD)
16+
public @interface Range {
17+
int minimum() default Integer.MIN_VALUE;
18+
19+
int maximum() default Integer.MAX_VALUE;
20+
}

json-schema/src/main/java/oracle/kubernetes/json/SchemaGenerator.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,33 @@ private Object getSubSchema(Field field) {
197197
sub.generateTypeIn(result, field.getType());
198198
String description = getDescription(field);
199199
if (description != null) result.put("description", description);
200-
Class<? extends java.lang.Enum> enumClass = getEnumClass(field);
201-
if (enumClass != null) addEnumValues(result, enumClass);
200+
if (isString(field.getType())) addStringRestrictions(result, field);
201+
if (isNumeric(field.getType())) addRange(result, field);
202202

203203
return result;
204204
}
205205

206+
private boolean isString(Class<?> type) {
207+
return type.equals(String.class);
208+
}
209+
210+
private boolean isNumeric(Class<?> type) {
211+
return Number.class.isAssignableFrom(type) || PRIMITIVE_NUMBERS.contains(type);
212+
}
213+
206214
private String getDescription(Field field) {
207215
Description description = field.getAnnotation(Description.class);
208216
return description != null ? description.value() : null;
209217
}
210218

219+
private void addStringRestrictions(Map<String, Object> result, Field field) {
220+
Class<? extends Enum> enumClass = getEnumClass(field);
221+
if (enumClass != null) addEnumValues(result, enumClass);
222+
223+
String pattern = getPattern(field);
224+
if (pattern != null) result.put("pattern", pattern);
225+
}
226+
211227
private Class<? extends java.lang.Enum> getEnumClass(Field field) {
212228
EnumClass annotation = field.getAnnotation(EnumClass.class);
213229
return annotation != null ? annotation.value() : null;
@@ -218,6 +234,19 @@ private void addEnumValues(
218234
result.put("enum", getEnumValues(enumClass));
219235
}
220236

237+
private String getPattern(Field field) {
238+
Pattern pattern = field.getAnnotation(Pattern.class);
239+
return pattern == null ? null : pattern.value();
240+
}
241+
242+
private void addRange(Map<String, Object> result, Field field) {
243+
Range annotation = field.getAnnotation(Range.class);
244+
if (annotation == null) return;
245+
246+
if (annotation.minimum() > Integer.MIN_VALUE) result.put("minimum", annotation.minimum());
247+
if (annotation.maximum() < Integer.MAX_VALUE) result.put("maximum", annotation.maximum());
248+
}
249+
221250
private class SubSchemaGenerator {
222251
Field field;
223252

@@ -227,9 +256,8 @@ private class SubSchemaGenerator {
227256

228257
private void generateTypeIn(Map<String, Object> result, Class<?> type) {
229258
if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) result.put("type", "boolean");
230-
else if (Number.class.isAssignableFrom(type) || PRIMITIVE_NUMBERS.contains(type))
231-
result.put("type", "number");
232-
else if (type.equals(String.class)) result.put("type", "string");
259+
else if (isNumeric(type)) result.put("type", "number");
260+
else if (isString(type)) result.put("type", "string");
233261
else if (type.isEnum()) generateEnumTypeIn(result, type);
234262
else if (type.isArray()) this.generateArrayTypeIn(result, type);
235263
else if (Collection.class.isAssignableFrom(type)) generateCollectionTypeIn(result);

json-schema/src/test/java/oracle/kubernetes/json/Directions.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

json-schema/src/test/java/oracle/kubernetes/json/SchemaGeneratorTest.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,64 @@ private enum TrafficLightColors {
111111

112112
@Test
113113
public void generateSchemaForEnumAnnotatedString() throws NoSuchFieldException {
114-
Object schema = generateForField(getClass().getDeclaredField("direction"));
114+
Object schema = generateForField(getClass().getDeclaredField("colorString"));
115115

116-
assertThat(schema, hasJsonPath("$.direction.type", equalTo("string")));
116+
assertThat(schema, hasJsonPath("$.colorString.type", equalTo("string")));
117117
assertThat(
118118
schema,
119-
hasJsonPath(
120-
"$.direction.enum", arrayContainingInAnyOrder("NORTH", "SOUTH", "EAST", "WEST")));
119+
hasJsonPath("$.colorString.enum", arrayContainingInAnyOrder("RED", "YELLOW", "GREEN")));
120+
}
121+
122+
@SuppressWarnings("unused")
123+
@EnumClass(TrafficLightColors.class)
124+
private String colorString;
125+
126+
@Test
127+
public void whenIntegerAnnotatedWithMinimumOnly_addToSchema() throws NoSuchFieldException {
128+
Object schema = generateForField(getClass().getDeclaredField("valueWithMinimum"));
129+
130+
assertThat(schema, hasJsonPath("$.valueWithMinimum.minimum", equalTo(7)));
131+
assertThat(schema, hasNoJsonPath("$.valueWithMinimum.maximum"));
132+
}
133+
134+
@SuppressWarnings("unused")
135+
@Range(minimum = 7)
136+
private int valueWithMinimum;
137+
138+
@Test
139+
public void whenIntegerAnnotatedWithMaximumOnly_addToSchema() throws NoSuchFieldException {
140+
Object schema = generateForField(getClass().getDeclaredField("valueWithMaximum"));
141+
142+
assertThat(schema, hasNoJsonPath("$.valueWithMaximum.minimum"));
143+
assertThat(schema, hasJsonPath("$.valueWithMaximum.maximum", equalTo(43)));
144+
}
145+
146+
@SuppressWarnings("unused")
147+
@Range(maximum = 43)
148+
private int valueWithMaximum;
149+
150+
@Test
151+
public void whenIntegerAnnotatedWithRange_addToSchema() throws NoSuchFieldException {
152+
Object schema = generateForField(getClass().getDeclaredField("valueWithRange"));
153+
154+
assertThat(schema, hasJsonPath("$.valueWithRange.minimum", equalTo(12)));
155+
assertThat(schema, hasJsonPath("$.valueWithRange.maximum", equalTo(85)));
156+
}
157+
158+
@SuppressWarnings("unused")
159+
@Range(minimum = 12, maximum = 85)
160+
private int valueWithRange;
161+
162+
@Test
163+
public void whenStringAnnotatedWithPatterne_addToSchema() throws NoSuchFieldException {
164+
Object schema = generateForField(getClass().getDeclaredField("codeName"));
165+
166+
assertThat(schema, hasJsonPath("$.codeName.pattern", equalTo("[A-Z][a-zA-Z_]*")));
121167
}
122168

123169
@SuppressWarnings("unused")
124-
@EnumClass(Directions.class)
125-
private String direction;
170+
@Pattern("[A-Z][a-zA-Z_]*")
171+
private String codeName;
126172

127173
@Test
128174
public void generateSchemaForAnnotatedDouble() throws NoSuchFieldException {

model/src/main/java/oracle/kubernetes/weblogic/domain/v2/Cluster.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
package oracle.kubernetes.weblogic.domain.v2;
66

7-
import com.google.gson.annotations.Expose;
8-
import com.google.gson.annotations.SerializedName;
97
import javax.annotation.Nonnull;
8+
import oracle.kubernetes.json.Description;
9+
import oracle.kubernetes.json.Range;
1010
import org.apache.commons.lang3.builder.EqualsBuilder;
1111
import org.apache.commons.lang3.builder.HashCodeBuilder;
1212
import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -18,13 +18,13 @@
1818
*/
1919
public class Cluster extends BaseConfiguration {
2020
/** The name of the cluster. Required. */
21-
@SerializedName("clusterName")
22-
@Expose
21+
@Description("The name of this cluster. Required")
22+
@Nonnull
2323
private String clusterName;
2424

2525
/** The number of replicas to run in the cluster, if specified. */
26-
@SerializedName("replicas")
27-
@Expose
26+
@Description("The number of managed servers to run in this cluster")
27+
@Range(minimum = 0)
2828
private Integer replicas;
2929

3030
public String getClusterName() {
@@ -35,7 +35,7 @@ public void setClusterName(@Nonnull String clusterName) {
3535
this.clusterName = clusterName;
3636
}
3737

38-
public Cluster withClusterName(@Nonnull String clusterName) {
38+
Cluster withClusterName(@Nonnull String clusterName) {
3939
setClusterName(clusterName);
4040
return this;
4141
}

model/src/main/java/oracle/kubernetes/weblogic/domain/v2/Domain.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@
2626
@SuppressWarnings("deprecation")
2727
public class Domain {
2828

29-
/** The default number of replicas for a cluster. */
30-
static final int DEFAULT_REPLICA_LIMIT = 2;
31-
3229
/**
3330
* APIVersion defines the versioned schema of this representation of an object. Servers should
3431
* convert recognized schemas to the latest internal value, and may reject unrecognized values.
@@ -45,7 +42,7 @@ public class Domain {
4542
*/
4643
@SerializedName("kind")
4744
@Expose
48-
@Description("The type of resourced. Should be 'Domain'")
45+
@Description("The type of resource. Should be 'Domain'")
4946
private String kind;
5047
/**
5148
* Standard object's metadata. More info:

model/src/main/java/oracle/kubernetes/weblogic/domain/v2/DomainSpec.java

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
import static oracle.kubernetes.weblogic.domain.v2.ConfigurationConstants.START_IF_NEEDED;
88

9-
import com.google.gson.annotations.Expose;
10-
import com.google.gson.annotations.SerializedName;
119
import io.kubernetes.client.models.V1LocalObjectReference;
1210
import io.kubernetes.client.models.V1SecretReference;
1311
import java.util.*;
@@ -17,6 +15,8 @@
1715
import javax.validation.constraints.NotNull;
1816
import oracle.kubernetes.json.Description;
1917
import oracle.kubernetes.json.EnumClass;
18+
import oracle.kubernetes.json.Pattern;
19+
import oracle.kubernetes.json.Range;
2020
import oracle.kubernetes.operator.ImagePullPolicy;
2121
import oracle.kubernetes.operator.KubernetesConstants;
2222
import oracle.kubernetes.weblogic.domain.EffectiveConfigurationFactory;
@@ -34,16 +34,12 @@ public class DomainSpec extends BaseConfiguration {
3434
private static final String LOG_HOME_DEFAULT_PATTERN = "/shared/logs/%s";
3535

3636
/** Domain unique identifier. Must be unique across the Kubernetes cluster. (Required) */
37-
@SerializedName("domainUID")
38-
@Expose
3937
@NotNull
38+
@Pattern("^[a-z0-9_.]{1,253}$")
4039
private String domainUID;
4140

4241
/** Domain name (Required) */
43-
@SerializedName("domainName")
44-
@Expose
45-
@NotNull
46-
private String domainName;
42+
@NotNull private String domainName;
4743

4844
/**
4945
* Domain home
@@ -54,62 +50,48 @@ public class DomainSpec extends BaseConfiguration {
5450
"The folder for the Weblogic Domain. (Not required)"
5551
+ "Defaults to /shared/domains/domains/domainUID if domainHomeInImage is false"
5652
+ "Defaults to /shared/domains/domain if domainHomeInImage is true")
57-
@SerializedName("domainHome")
58-
@Expose
5953
private String domainHome;
6054

6155
/**
6256
* Reference to secret containing domain administrator username and password. Secret must contain
6357
* keys names 'username' and 'password' (Required)
6458
*/
65-
@SerializedName("adminSecret")
66-
@Expose
67-
@Valid
68-
@NotNull
69-
private V1SecretReference adminSecret;
59+
@Valid @NotNull private V1SecretReference adminSecret;
7060

7161
/**
7262
* Admin server name. Note: Possibly temporary as we could find this value through domain home
7363
* inspection. (Required)
7464
*/
75-
@SerializedName("asName")
76-
@Expose
77-
@NotNull
78-
private String asName;
65+
@NotNull private String asName;
7966

8067
/**
8168
* Administration server port. Note: Possibly temporary as we could find this value through domain
8269
* home inspection. (Required)
8370
*/
84-
@SerializedName("asPort")
85-
@Expose
8671
@NotNull
72+
@Range(minimum = 100)
8773
private Integer asPort;
8874

8975
/**
9076
* The in-pod name of the directory to store the domain, node manager, server logs, and server
9177
* .out files in.
9278
*/
93-
@SerializedName("logHome")
94-
@Expose
79+
@Description(
80+
"The in-pod name of the directory in which to store the domain, node manager, server logs, and server *.out files")
9581
private String logHome;
9682

9783
/**
9884
* Whether the log home is enabled.
9985
*
10086
* @since 2.0
10187
*/
102-
@SerializedName("logHomeEnabled")
103-
@Expose
10488
@Description(
10589
"Specified whether the log home folder is enabled (Not required). "
10690
+ "Defaults to true if domainHomeInImage is false. "
10791
+ "Defaults to false if domainHomeInImage is true. ")
10892
private Boolean logHomeEnabled; // Boolean object, null if unspecified
10993

11094
/** Whether to include the server .out file to the pod's stdout. Default is true. */
111-
@SerializedName("includeServerOutInPodLog")
112-
@Expose
11395
@Description("If true (the default), the server .out file will be included in the pod's stdout")
11496
private Boolean includeServerOutInPodLog;
11597

@@ -121,8 +103,6 @@ public class DomainSpec extends BaseConfiguration {
121103
@Description(
122104
"The Weblogic Docker image; required when domainHomeInImage is true; "
123105
+ "otherwise, defaults to store/oracle/weblogic:12.2.1.3")
124-
@SerializedName("image")
125-
@Expose
126106
private String image;
127107

128108
/**
@@ -149,8 +129,6 @@ public class DomainSpec extends BaseConfiguration {
149129
* @since 2.0
150130
*/
151131
@Description("A list of image pull secrets for the WebLogic Docker image.")
152-
@SerializedName("imagePullSecrets")
153-
@Expose
154132
private List<V1LocalObjectReference> imagePullSecrets;
155133

156134
/**
@@ -159,6 +137,7 @@ public class DomainSpec extends BaseConfiguration {
159137
*/
160138
@Description(
161139
"The number of managed servers to run in any cluster that does not specify a replica count")
140+
@Range(minimum = 0)
162141
private Integer replicas;
163142

164143
/**

0 commit comments

Comments
 (0)