38
38
import java .time .LocalTime ;
39
39
import java .util .ArrayList ;
40
40
import java .util .Arrays ;
41
+ import java .util .Collection ;
41
42
import java .util .HashSet ;
42
43
import java .util .List ;
43
44
import java .util .Map ;
50
51
import java .util .concurrent .atomic .AtomicInteger ;
51
52
import java .util .concurrent .atomic .AtomicLong ;
52
53
import java .util .function .Predicate ;
54
+ import java .util .stream .Collectors ;
53
55
import java .util .stream .Stream ;
54
56
55
57
import io .swagger .v3 .core .util .PrimitiveType ;
56
58
import io .swagger .v3 .oas .annotations .Parameter ;
59
+ import io .swagger .v3 .oas .annotations .media .Schema ;
57
60
58
61
import org .springframework .core .GenericTypeResolver ;
59
62
import org .springframework .core .MethodParameter ;
60
63
64
+ import static org .springdoc .core .service .AbstractRequestService .hasNotNullAnnotation ;
61
65
import static org .springdoc .core .utils .Constants .DOT ;
62
66
63
67
/**
64
68
* The type Method parameter pojo extractor.
65
69
*
66
- * @author bnasslahsen
70
+ * @author bnasslahsen, michael.clarke
67
71
*/
68
72
public class MethodParameterPojoExtractor {
69
73
@@ -113,20 +117,21 @@ private MethodParameterPojoExtractor() {
113
117
* @return the stream
114
118
*/
115
119
static Stream <MethodParameter > extractFrom (Class <?> clazz ) {
116
- return extractFrom (clazz , "" );
120
+ return extractFrom (clazz , "" , true );
117
121
}
118
122
119
123
/**
120
124
* Extract from stream.
121
125
*
122
126
* @param clazz the clazz
123
127
* @param fieldNamePrefix the field name prefix
128
+ * @param parentRequired whether the field that hold the class currently being inspected was required or optional
124
129
* @return the stream
125
130
*/
126
- private static Stream <MethodParameter > extractFrom (Class <?> clazz , String fieldNamePrefix ) {
131
+ private static Stream <MethodParameter > extractFrom (Class <?> clazz , String fieldNamePrefix , boolean parentRequired ) {
127
132
return allFieldsOf (clazz ).stream ()
128
133
.filter (field -> !field .getType ().equals (clazz ))
129
- .flatMap (f -> fromGetterOfField (clazz , f , fieldNamePrefix ))
134
+ .flatMap (f -> fromGetterOfField (clazz , f , fieldNamePrefix , parentRequired ))
130
135
.filter (Objects ::nonNull );
131
136
}
132
137
@@ -136,20 +141,92 @@ private static Stream<MethodParameter> extractFrom(Class<?> clazz, String fieldN
136
141
* @param paramClass the param class
137
142
* @param field the field
138
143
* @param fieldNamePrefix the field name prefix
144
+ * @param parentRequired whether the field that holds the class currently being examined was required or optional
139
145
* @return the stream
140
146
*/
141
- private static Stream <MethodParameter > fromGetterOfField (Class <?> paramClass , Field field , String fieldNamePrefix ) {
147
+ private static Stream <MethodParameter > fromGetterOfField (Class <?> paramClass , Field field , String fieldNamePrefix , boolean parentRequired ) {
142
148
Class <?> type = extractType (paramClass , field );
143
149
144
150
if (Objects .isNull (type ))
145
151
return Stream .empty ();
146
152
147
153
if (isSimpleType (type ))
148
- return fromSimpleClass (paramClass , field , fieldNamePrefix );
154
+ return fromSimpleClass (paramClass , field , fieldNamePrefix , parentRequired );
149
155
else {
150
- String prefix = fieldNamePrefix + field .getName () + DOT ;
151
- return extractFrom (type , prefix );
156
+ Parameter parameter = field .getAnnotation (Parameter .class );
157
+ Schema schema = field .getAnnotation (Schema .class );
158
+ boolean visible = resolveVisible (parameter , schema );
159
+ if (!visible ) {
160
+ return Stream .empty ();
161
+ }
162
+ String prefix = fieldNamePrefix + resolveName (parameter , schema ).orElse (field .getName ()) + DOT ;
163
+ boolean isNullable = isNullable (field .getDeclaredAnnotations ());
164
+ return extractFrom (type , prefix , parentRequired && resolveRequired (schema , parameter , isNullable ));
165
+ }
166
+ }
167
+
168
+ private static Optional <String > resolveName (Parameter parameter , Schema schema ) {
169
+ if (parameter != null ) {
170
+ return resolveNameFromParameter (parameter );
171
+ }
172
+ if (schema != null ) {
173
+ return resolveNameFromSchema (schema );
174
+ }
175
+ return Optional .empty ();
176
+ }
177
+
178
+ private static Optional <String > resolveNameFromParameter (Parameter parameter ) {
179
+ if (parameter .name ().isEmpty ()) {
180
+ return Optional .empty ();
181
+ }
182
+ return Optional .of (parameter .name ());
183
+ }
184
+
185
+ private static Optional <String > resolveNameFromSchema (Schema schema ) {
186
+ if (schema .name ().isEmpty ()) {
187
+ return Optional .empty ();
188
+ }
189
+ return Optional .of (schema .name ());
190
+ }
191
+
192
+ private static boolean resolveVisible (Parameter parameter , Schema schema ) {
193
+ if (parameter != null ) {
194
+ return !parameter .hidden ();
195
+ }
196
+ if (schema != null ) {
197
+ return !schema .hidden ();
198
+ }
199
+ return true ;
200
+ }
201
+
202
+ private static boolean resolveRequired (Schema schema , Parameter parameter , boolean nullable ) {
203
+ if (parameter != null ) {
204
+ return resolveRequiredFromParameter (parameter , nullable );
205
+ }
206
+ if (schema != null ) {
207
+ return resolveRequiredFromSchema (schema , nullable );
152
208
}
209
+ return !nullable ;
210
+ }
211
+
212
+ private static boolean resolveRequiredFromParameter (Parameter parameter , boolean nullable ) {
213
+ if (parameter .required ()) {
214
+ return true ;
215
+ }
216
+ return !nullable ;
217
+ }
218
+
219
+ private static boolean resolveRequiredFromSchema (Schema schema , boolean nullable ) {
220
+ if (schema .required ()) {
221
+ return true ;
222
+ }
223
+ else if (schema .requiredMode () == Schema .RequiredMode .REQUIRED ) {
224
+ return true ;
225
+ }
226
+ else if (schema .requiredMode () == Schema .RequiredMode .NOT_REQUIRED ) {
227
+ return false ;
228
+ }
229
+ return !nullable ;
153
230
}
154
231
155
232
/**
@@ -181,19 +258,20 @@ private static Class<?> extractType(Class<?> paramClass, Field field) {
181
258
* @param fieldNamePrefix the field name prefix
182
259
* @return the stream
183
260
*/
184
- private static Stream <MethodParameter > fromSimpleClass (Class <?> paramClass , Field field , String fieldNamePrefix ) {
261
+ private static Stream <MethodParameter > fromSimpleClass (Class <?> paramClass , Field field , String fieldNamePrefix , boolean isParentRequired ) {
185
262
Annotation [] fieldAnnotations = field .getDeclaredAnnotations ();
186
263
try {
187
264
Parameter parameter = field .getAnnotation (Parameter .class );
188
- boolean isNotRequired = parameter == null || !parameter .required ();
265
+ Schema schema = field .getAnnotation (Schema .class );
266
+ boolean isNullable = isNullable (fieldAnnotations );
267
+ boolean isNotRequired = !(isParentRequired && resolveRequired (schema , parameter , isNullable ));
189
268
if (paramClass .getSuperclass () != null && paramClass .isRecord ()) {
190
269
return Stream .of (paramClass .getRecordComponents ())
191
270
.filter (d -> d .getName ().equals (field .getName ()))
192
271
.map (RecordComponent ::getAccessor )
193
272
.map (method -> new MethodParameter (method , -1 ))
194
273
.map (methodParameter -> DelegatingMethodParameter .changeContainingClass (methodParameter , paramClass ))
195
274
.map (param -> new DelegatingMethodParameter (param , fieldNamePrefix + field .getName (), fieldAnnotations , param .getMethodAnnotations (), true , isNotRequired ));
196
-
197
275
}
198
276
else
199
277
return Stream .of (Introspector .getBeanInfo (paramClass ).getPropertyDescriptors ())
@@ -273,4 +351,17 @@ public static void removeSimpleTypes(Class<?>... classes) {
273
351
SIMPLE_TYPES .removeAll (Arrays .asList (classes ));
274
352
}
275
353
354
+ /**
355
+ * Is nullable boolean.
356
+ *
357
+ * @param fieldAnnotations the field annotations
358
+ * @return the boolean
359
+ */
360
+ private static boolean isNullable (Annotation [] fieldAnnotations ) {
361
+ Collection <String > annotationSimpleNames = Arrays .stream (fieldAnnotations )
362
+ .map (Annotation ::annotationType )
363
+ .map (Class ::getSimpleName )
364
+ .collect (Collectors .toSet ());
365
+ return !hasNotNullAnnotation (annotationSimpleNames );
366
+ }
276
367
}
0 commit comments