1717package org .springframework .cloud .contract .verifier .builder ;
1818
1919import java .math .BigDecimal ;
20+ import java .lang .reflect .Array ;
21+ import java .util .ArrayList ;
2022import java .util .List ;
2123import java .util .Map ;
2224import java .util .Optional ;
2628import com .jayway .jsonpath .JsonPath ;
2729import com .jayway .jsonpath .PathNotFoundException ;
2830import groovy .json .JsonOutput ;
29- import org .apache .commons .beanutils .PropertyUtilsBean ;
30-
31+ import org .springframework .beans .BeanWrapperImpl ;
3132import org .springframework .cloud .contract .spec .Contract ;
3233import org .springframework .cloud .contract .spec .ContractTemplate ;
3334import org .springframework .cloud .contract .spec .internal .BodyMatcher ;
@@ -298,7 +299,7 @@ private static Object retrieveObjectByPath(Object body, String path) {
298299 + contractTemplate .escapedClosingTemplate ();
299300 }
300301 try {
301- Object result = new PropertyUtilsBean (). getProperty (templateModel , justEntry );
302+ Object result = resolveTemplateModelEntry (templateModel , justEntry );
302303 // Path from the Test model is an object and we'd like to return its
303304 // String representation
304305 if (FROM_REQUEST_PATH .equals (justEntry )) {
@@ -314,6 +315,105 @@ private static Object retrieveObjectByPath(Object body, String path) {
314315 };
315316 }
316317
318+ private Object resolveTemplateModelEntry (TestSideRequestTemplateModel templateModel , String propertyPath ) {
319+ Object current = templateModel ;
320+ for (String token : tokenizePropertyPath (propertyPath )) {
321+ current = resolveNextToken (current , token );
322+ }
323+ return current ;
324+ }
325+
326+ private Object resolveNextToken (Object current , String token ) {
327+ if (current == null ) {
328+ throw new IllegalStateException ("Unable to resolve property for null value" );
329+ }
330+ if (current instanceof Map ) {
331+ Map <?, ?> map = (Map <?, ?>) current ;
332+ String key = unquote (token );
333+ if (!map .containsKey (key )) {
334+ throw new IllegalStateException ("Missing map key [" + key + "]" );
335+ }
336+ return map .get (key );
337+ }
338+ if (current instanceof List ) {
339+ int index = parseIndex (token );
340+ List <?> list = (List <?>) current ;
341+ if (index < 0 || index >= list .size ()) {
342+ throw new IllegalStateException ("Index [" + index + "] out of bounds" );
343+ }
344+ return list .get (index );
345+ }
346+ if (current .getClass ().isArray ()) {
347+ int index = parseIndex (token );
348+ int length = Array .getLength (current );
349+ if (index < 0 || index >= length ) {
350+ throw new IllegalStateException ("Index [" + index + "] out of bounds" );
351+ }
352+ return Array .get (current , index );
353+ }
354+ BeanWrapperImpl wrapper = new BeanWrapperImpl (current );
355+ if (!wrapper .isReadableProperty (token )) {
356+ throw new IllegalStateException ("No readable property [" + token + "]" );
357+ }
358+ return wrapper .getPropertyValue (token );
359+ }
360+
361+ private int parseIndex (String token ) {
362+ try {
363+ return Integer .parseInt (token );
364+ }
365+ catch (NumberFormatException ex ) {
366+ throw new IllegalStateException ("Invalid index token [" + token + "]" , ex );
367+ }
368+ }
369+
370+ private List <String > tokenizePropertyPath (String propertyPath ) {
371+ List <String > tokens = new ArrayList <>();
372+ String [] segments = propertyPath .split ("\\ ." );
373+ for (String segment : segments ) {
374+ addTokens (segment , tokens );
375+ }
376+ return tokens ;
377+ }
378+
379+ private void addTokens (String segment , List <String > tokens ) {
380+ int index = 0 ;
381+ while (index < segment .length ()) {
382+ int bracketStart = segment .indexOf ('[' , index );
383+ if (bracketStart == -1 ) {
384+ String token = segment .substring (index );
385+ if (!token .isEmpty ()) {
386+ tokens .add (token );
387+ }
388+ return ;
389+ }
390+ String before = segment .substring (index , bracketStart );
391+ if (!before .isEmpty ()) {
392+ tokens .add (before );
393+ }
394+ int bracketEnd = segment .indexOf (']' , bracketStart );
395+ if (bracketEnd == -1 ) {
396+ String remainder = segment .substring (bracketStart + 1 );
397+ if (!remainder .isEmpty ()) {
398+ tokens .add (remainder );
399+ }
400+ return ;
401+ }
402+ String inside = segment .substring (bracketStart + 1 , bracketEnd );
403+ if (!inside .isEmpty ()) {
404+ tokens .add (inside );
405+ }
406+ index = bracketEnd + 1 ;
407+ }
408+ }
409+
410+ private String unquote (String value ) {
411+ if ((value .startsWith ("'" ) && value .endsWith ("'" )) || (value .startsWith ("\" " ) && value .endsWith ("\" " ))) {
412+ return value .substring (1 , value .length () - 1 );
413+ }
414+ return value ;
415+ }
416+
317417 private static String minus (CharSequence self , Object target ) {
318418 String s = self .toString ();
319419 String text = target .toString ();
0 commit comments