You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2020/03/16 10:48:35 UTC

[servicecomb-java-chassis] branch 1.3.x updated (fbf7cae -> d59f2a6)

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a change to branch 1.3.x
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git.


    from fbf7cae  [SCB-1638]servicecenter do not handle websocket PING/PONG message (#1450)
     new a1163de  [SCB-1793] use @RequestHeader(value ="xxx") and aggregatedParam at same time, it will throw null pointer exception
     new d59f2a6  [SCB-1793] use @RequestHeader(value ="xxx") : add ut

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../it/schema/objectParams/QueryObjectModel.java   |  39 +++--
 .../org/apache/servicecomb/it/ConsumerMain.java    |   2 +
 .../objectParams/TestSpringMVCObjectParam.java     | 172 +++++++++++++++++++++
 .../schema/{EmptyScheam.java => EmptySchema.java}  |   6 +-
 .../objectParams/SpringMVCObjectParamSchema.java   |  79 ++++++++++
 .../arguments/ArgumentsMapperFactory.java          |  47 ++++--
 6 files changed, 305 insertions(+), 40 deletions(-)
 copy foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/model/User.java => integration-tests/it-common/src/main/java/org/apache/servicecomb/it/schema/objectParams/QueryObjectModel.java (67%)
 create mode 100644 integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/objectParams/TestSpringMVCObjectParam.java
 rename integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/{EmptyScheam.java => EmptySchema.java} (90%)
 create mode 100644 integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/objectParams/SpringMVCObjectParamSchema.java


[servicecomb-java-chassis] 02/02: [SCB-1793] use @RequestHeader(value ="xxx") : add ut

Posted by li...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch 1.3.x
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git

commit d59f2a6739de8a21d0db7f83bcd692389db2fcdf
Author: heyile <25...@qq.com>
AuthorDate: Mon Mar 16 17:11:00 2020 +0800

    [SCB-1793] use @RequestHeader(value ="xxx") : add ut
---
 .../it/schema/objectParams/QueryObjectModel.java}  |  43 +++++-
 .../org/apache/servicecomb/it/ConsumerMain.java    |   2 +
 .../objectParams/TestSpringMVCObjectParam.java     | 172 +++++++++++++++++++++
 .../schema/{EmptyScheam.java => EmptySchema.java}  |   6 +-
 .../objectParams/SpringMVCObjectParamSchema.java   |  79 ++++++++++
 5 files changed, 293 insertions(+), 9 deletions(-)

diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptyScheam.java b/integration-tests/it-common/src/main/java/org/apache/servicecomb/it/schema/objectParams/QueryObjectModel.java
similarity index 53%
copy from integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptyScheam.java
copy to integration-tests/it-common/src/main/java/org/apache/servicecomb/it/schema/objectParams/QueryObjectModel.java
index 583c361..094014d 100644
--- a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptyScheam.java
+++ b/integration-tests/it-common/src/main/java/org/apache/servicecomb/it/schema/objectParams/QueryObjectModel.java
@@ -14,13 +14,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.servicecomb.it.schema.objectParams;
 
-package org.apache.servicecomb.it.schema;
+public class QueryObjectModel {
+  private int index;
 
-import org.apache.servicecomb.provider.rest.common.RestSchema;
-import org.springframework.web.bind.annotation.RequestMapping;
+  private String name;
 
-@RestSchema(schemaId = "EmptyScheam")
-@RequestMapping(path = "/v1/EmptyScheam")
-public class EmptyScheam {
+  public QueryObjectModel() {
+  }
+
+  public QueryObjectModel(int index, String name) {
+    this.index = index;
+    this.name = name;
+  }
+
+  public int getIndex() {
+    return index;
+  }
+
+  public QueryObjectModel setIndex(int index) {
+    this.index = index;
+    return this;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public QueryObjectModel setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return "QueryObject{" +
+        "index=" + index +
+        ", name='" + name + '\'' +
+        '}';
+  }
 }
diff --git a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/ConsumerMain.java b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/ConsumerMain.java
index 63772a2..924c0e8 100644
--- a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/ConsumerMain.java
+++ b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/ConsumerMain.java
@@ -49,6 +49,7 @@ import org.apache.servicecomb.it.testcase.TestTrace;
 import org.apache.servicecomb.it.testcase.TestTraceEdge;
 import org.apache.servicecomb.it.testcase.TestUpload;
 import org.apache.servicecomb.it.testcase.base.TestGeneric;
+import org.apache.servicecomb.it.testcase.objectParams.TestSpringMVCObjectParam;
 import org.apache.servicecomb.it.testcase.thirdparty.Test3rdPartyInvocation;
 
 public class ConsumerMain {
@@ -103,6 +104,7 @@ public class ConsumerMain {
   }
 
   private static void runShareTestCases() throws Throwable {
+    ITJUnitUtils.runWithHighwayAndRest(TestSpringMVCObjectParam.class);
     ITJUnitUtils.runWithHighwayAndRest(TestChangeTransport.class);
     ITJUnitUtils.runWithHighwayAndRest(TestDataTypePrimitive.class);
     ITJUnitUtils.runWithHighwayAndRest(TestAnnotatedAttribute.class);
diff --git a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/objectParams/TestSpringMVCObjectParam.java b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/objectParams/TestSpringMVCObjectParam.java
new file mode 100644
index 0000000..d4404b5
--- /dev/null
+++ b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/objectParams/TestSpringMVCObjectParam.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.it.testcase.objectParams;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.servicecomb.it.Consumers;
+import org.apache.servicecomb.it.schema.objectParams.QueryObjectModel;
+import org.junit.Test;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+
+public class TestSpringMVCObjectParam {
+  interface SpringMVCObjectIntf {
+    String testQueryObjectParam(int index, String name);
+
+    String testQueryObjectWithHeader(String prefix, int index, String name);
+
+    String testQueryObjectWithHeaderName(String prefix, int index, String name);
+
+    String testQueryObjectWithHeaderValue(String prefix, int index, String name);
+
+    String testQueryObjectWithHeaderValueAndName(String prefix, String suffix, int index, String name);
+
+    String testQueryObjectWithParam(String prefix, int index, String name);
+
+    String testQueryObjectWithParamName(String prefix, int index, String name);
+
+    String testQueryObjectWithParamValue(String prefix, int index, String name);
+  }
+
+  private String prefix = "prefix-";
+
+  private String suffix = "-suffix";
+
+  private QueryObjectModel queryModel = new QueryObjectModel(23, "demo");
+
+  private String queryParam = "index=23&name=demo";
+
+  private static Consumers<SpringMVCObjectIntf> consumersSpringmvc = new Consumers<>("springMVCObjectParamSchema",
+      SpringMVCObjectIntf.class);
+
+  @Test
+  public void testQueryObjectParam_rt() {
+    assertEquals(queryModel.toString(), consumersSpringmvc.getSCBRestTemplate()
+        .getForObject("/testQueryObjectParam?" + queryParam, String.class));
+  }
+
+  @Test
+  public void testQueryObjectParam_pojo() {
+    assertEquals(queryModel.toString(), consumersSpringmvc.getIntf().testQueryObjectParam(23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithHeader_rt() {
+    HttpHeaders headers = new HttpHeaders();
+    headers.add("prefix", prefix);
+    assertEquals(prefix + queryModel.toString(),
+        queryObjectHeader(consumersSpringmvc, headers, "/testQueryObjectWithHeader?" + queryParam));
+  }
+
+  @Test
+  public void testQueryObjectWithHeader_pojo() {
+    assertEquals(prefix + queryModel.toString(),
+        consumersSpringmvc.getIntf().testQueryObjectWithHeader(prefix, 23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithHeaderName_rt() {
+    HttpHeaders headers = new HttpHeaders();
+    headers.add("prefix", prefix);
+    assertEquals(prefix + queryModel.toString(),
+        queryObjectHeader(consumersSpringmvc, headers, "/testQueryObjectWithHeaderName?" + queryParam));
+  }
+
+  @Test
+  public void testQueryObjectWithHeaderName_pojo() {
+    assertEquals(prefix + queryModel.toString(),
+        consumersSpringmvc.getIntf().testQueryObjectWithHeaderName(prefix, 23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithHeaderValue_rt() {
+    HttpHeaders headers = new HttpHeaders();
+    headers.add("prefix", prefix);
+    assertEquals(prefix + queryModel.toString(),
+        queryObjectHeader(consumersSpringmvc, headers, "/testQueryObjectWithHeaderValue?" + queryParam));
+  }
+
+  @Test
+  public void testQueryObjectWithHeaderValue_pojo() {
+    assertEquals(prefix + queryModel.toString(),
+        consumersSpringmvc.getIntf().testQueryObjectWithHeaderValue(prefix, 23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithHeaderValueAndName_rt() {
+    HttpHeaders headers = new HttpHeaders();
+    headers.add("prefix", prefix);
+    headers.add("suffix", suffix);
+    assertEquals(prefix + queryModel.toString() + suffix,
+        queryObjectHeader(consumersSpringmvc, headers, "/testQueryObjectWithHeaderValueAndName?" + queryParam));
+  }
+
+  @Test
+  public void testQueryObjectWithHeaderValueAndName_pojo() {
+    assertEquals(prefix + queryModel.toString() + suffix,
+        consumersSpringmvc.getIntf().testQueryObjectWithHeaderValueAndName(prefix, suffix, 23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithParam_rt() {
+    assertEquals(prefix + queryModel.toString(), consumersSpringmvc.getSCBRestTemplate()
+        .getForObject("/testQueryObjectWithParam?prefix=" + prefix + "&" + queryParam, String.class));
+  }
+
+  @Test
+  public void testQueryObjectWithParam_pojo() {
+    assertEquals(prefix + queryModel.toString(),
+        consumersSpringmvc.getIntf().testQueryObjectWithParam(prefix, 23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithParamName_rt() {
+    assertEquals(prefix + queryModel.toString(), consumersSpringmvc.getSCBRestTemplate()
+        .getForObject("/testQueryObjectWithParamName?prefix=" + prefix + "&" + queryParam, String.class));
+  }
+
+  @Test
+  public void testQueryObjectWithParamName_pojo() {
+    assertEquals(prefix + queryModel.toString(),
+        consumersSpringmvc.getIntf().testQueryObjectWithParamName(prefix, 23, "demo"));
+  }
+
+  @Test
+  public void testQueryObjectWithParamValue_rt() {
+    assertEquals(prefix + queryModel.toString(), consumersSpringmvc.getSCBRestTemplate()
+        .getForObject("/testQueryObjectWithParamValue?prefix=" + prefix + "&" + queryParam, String.class));
+  }
+
+  @Test
+  public void testQueryObjectWithParamValue_pojo() {
+    assertEquals(prefix + queryModel.toString(),
+        consumersSpringmvc.getIntf().testQueryObjectWithParamValue(prefix, 23, "demo"));
+  }
+
+  protected String queryObjectHeader(Consumers<SpringMVCObjectIntf> consumers, HttpHeaders headers, String url) {
+    HttpEntity<?> entity = new HttpEntity<>(headers);
+    ResponseEntity<String> response = consumers.getSCBRestTemplate()
+        .exchange(url,
+            HttpMethod.GET,
+            entity,
+            String.class);
+    return response.getBody();
+  }
+}
diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptyScheam.java b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptySchema.java
similarity index 90%
rename from integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptyScheam.java
rename to integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptySchema.java
index 583c361..064a419 100644
--- a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptyScheam.java
+++ b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/EmptySchema.java
@@ -20,7 +20,7 @@ package org.apache.servicecomb.it.schema;
 import org.apache.servicecomb.provider.rest.common.RestSchema;
 import org.springframework.web.bind.annotation.RequestMapping;
 
-@RestSchema(schemaId = "EmptyScheam")
-@RequestMapping(path = "/v1/EmptyScheam")
-public class EmptyScheam {
+@RestSchema(schemaId = "EmptySchema")
+@RequestMapping(path = "/v1/EmptySchema")
+public class EmptySchema {
 }
diff --git a/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/objectParams/SpringMVCObjectParamSchema.java b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/objectParams/SpringMVCObjectParamSchema.java
new file mode 100644
index 0000000..87e745d
--- /dev/null
+++ b/integration-tests/it-producer/src/main/java/org/apache/servicecomb/it/schema/objectParams/SpringMVCObjectParamSchema.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicecomb.it.schema.objectParams;
+
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@RestSchema(schemaId = "springMVCObjectParamSchema")
+@RequestMapping(path = "/v1/springMVCObjectParamSchema")
+public class SpringMVCObjectParamSchema {
+
+  /**
+   * <a href="https://issues.apache.org/jira/browse/SCB-708">SCB-708</a> SpringMVC only
+   */
+  @GetMapping("testQueryObjectParam")
+  public String testQueryObjectParam(QueryObjectModel QueryObjectModel) {
+    return QueryObjectModel.toString();
+  }
+
+  @GetMapping("testQueryObjectWithHeader")
+  public String testQueryObjectWithHeader(@RequestHeader("prefix") String prefix, QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString();
+  }
+
+  @GetMapping("testQueryObjectWithHeaderName")
+  public String testQueryObjectWithHeaderName(@RequestHeader(name = "prefix") String prefix,
+      QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString();
+  }
+
+  /**
+   * <a href="https://issues.apache.org/jira/browse/SCB-1793">SCB-1793</a> support @RequestHeader(value ="xxx")
+   */
+  @GetMapping("testQueryObjectWithHeaderValue")
+  public String testQueryObjectWithHeaderValue(@RequestHeader(value = "prefix") String prefix,
+      QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString();
+  }
+
+  @GetMapping("testQueryObjectWithHeaderValueAndName")
+  public String testQueryObjectWithHeaderValueAndName(@RequestHeader(name = "prefix") String prefix,
+      @RequestHeader(value = "suffix") String suffix, QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString() + suffix;
+  }
+
+  @GetMapping("testQueryObjectWithParam")
+  public String testQueryObjectWithParam(@RequestParam("prefix") String prefix, QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString();
+  }
+
+  @GetMapping("testQueryObjectWithParamName")
+  public String testQueryObjectWithParamName(@RequestParam(name = "prefix") String prefix,
+      QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString();
+  }
+
+  @GetMapping("testQueryObjectWithParamValue")
+  public String testQueryObjectWithParamValue(@RequestParam(value = "prefix") String prefix,
+      QueryObjectModel QueryObjectModel) {
+    return prefix + QueryObjectModel.toString();
+  }
+}


[servicecomb-java-chassis] 01/02: [SCB-1793] use @RequestHeader(value ="xxx") and aggregatedParam at same time, it will throw null pointer exception

Posted by li...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch 1.3.x
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git

commit a1163de64b44d84fedc2d2cfe526c523df759aab
Author: heyile <25...@qq.com>
AuthorDate: Wed Mar 4 10:14:32 2020 +0800

    [SCB-1793] use @RequestHeader(value ="xxx") and aggregatedParam at same time, it will throw null pointer exception
---
 .../arguments/ArgumentsMapperFactory.java          | 47 ++++++++++++++--------
 1 file changed, 30 insertions(+), 17 deletions(-)

diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java
index eba4d88..45e721e 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java
@@ -33,6 +33,7 @@ import javax.ws.rs.HeaderParam;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
 import org.apache.servicecomb.swagger.invocation.InvocationType;
 import org.apache.servicecomb.swagger.invocation.converter.Converter;
@@ -167,38 +168,50 @@ public abstract class ArgumentsMapperFactory<T> {
   }
 
   public static String retrieveVisibleParamName(Annotation annotation) {
-    if (CookieParam.class.isInstance(annotation)) {
+    if (annotation instanceof CookieParam) {
       return ((CookieParam) annotation).value();
     }
-    if (CookieValue.class.isInstance(annotation)) {
-      return ((CookieValue) annotation).name();
+    if (annotation instanceof CookieValue) {
+      return StringUtils.isEmpty(((CookieValue) annotation).name()) ?
+          ((CookieValue) annotation).value() :
+          ((CookieValue) annotation).name();
     }
-    if (FormParam.class.isInstance(annotation)) {
+    if (annotation instanceof FormParam) {
       return ((FormParam) annotation).value();
     }
-    if (HeaderParam.class.isInstance(annotation)) {
+    if (annotation instanceof HeaderParam) {
       return ((HeaderParam) annotation).value();
     }
-    if (PathParam.class.isInstance(annotation)) {
+    if (annotation instanceof PathParam) {
       return ((PathParam) annotation).value();
     }
-    if (PathVariable.class.isInstance(annotation)) {
-      return ((PathVariable) annotation).value();
+    if (annotation instanceof PathVariable) {
+      return StringUtils.isEmpty(((PathVariable) annotation).name()) ?
+          ((PathVariable) annotation).value() :
+          ((PathVariable) annotation).name();
     }
-    if (QueryParam.class.isInstance(annotation)) {
+    if (annotation instanceof QueryParam) {
       return ((QueryParam) annotation).value();
     }
-    if (RequestAttribute.class.isInstance(annotation)) {
-      return ((RequestAttribute) annotation).name();
+    if (annotation instanceof RequestAttribute) {
+      return StringUtils.isEmpty(((RequestAttribute) annotation).name()) ?
+          ((RequestAttribute) annotation).value() :
+          ((RequestAttribute) annotation).name();
     }
-    if (RequestHeader.class.isInstance(annotation)) {
-      return ((RequestHeader) annotation).name();
+    if (annotation instanceof RequestHeader) {
+      return StringUtils.isEmpty(((RequestHeader) annotation).name()) ?
+          ((RequestHeader) annotation).value() :
+          ((RequestHeader) annotation).name();
     }
-    if (RequestParam.class.isInstance(annotation)) {
-      return ((RequestParam) annotation).name();
+    if (annotation instanceof RequestParam) {
+      return StringUtils.isEmpty(((RequestParam) annotation).name()) ?
+          ((RequestParam) annotation).value() :
+          ((RequestParam) annotation).name();
     }
-    if (RequestPart.class.isInstance(annotation)) {
-      return ((RequestPart) annotation).name();
+    if (annotation instanceof RequestPart) {
+      return StringUtils.isEmpty(((RequestPart) annotation).name()) ?
+          ((RequestPart) annotation).value() :
+          ((RequestPart) annotation).name();
     }
 
     return null;