summaryrefslogtreecommitdiffstats
path: root/bpmn/MSOCommonBPMN
diff options
context:
space:
mode:
Diffstat (limited to 'bpmn/MSOCommonBPMN')
-rw-r--r--bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy37
-rw-r--r--bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy29
-rw-r--r--bpmn/MSOCommonBPMN/src/main/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetup.java14
-rw-r--r--bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy199
-rw-r--r--bpmn/MSOCommonBPMN/src/test/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetupTest.java16
5 files changed, 268 insertions, 27 deletions
diff --git a/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy
index 5c935e9081..250cdda0a1 100644
--- a/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy
+++ b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtil.groovy
@@ -19,29 +19,30 @@
*/
package org.onap.so.bpmn.common.scripts
-import org.camunda.bpm.engine.delegate.BpmnError
+
+
import org.camunda.bpm.engine.delegate.DelegateExecution
-import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor;
+import org.onap.logging.ref.slf4j.ONAPLogConstants
import org.onap.so.client.HttpClient
+import org.onap.so.client.HttpClientFactory
import org.onap.so.logger.MsoLogger
-import org.apache.commons.lang3.StringEscapeUtils
-import java.util.regex.Matcher
-import java.util.regex.Pattern
+import org.onap.so.utils.TargetEntity
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
-import org.onap.so.utils.TargetEntity
+import java.util.regex.Matcher
+import java.util.regex.Pattern
class ExternalAPIUtil {
String Prefix="EXTAPI_"
- public MsoUtils utils = new MsoUtils()
-
- ExceptionUtil exceptionUtil = new ExceptionUtil()
-
private static final MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL, ExternalAPIUtil.class)
+ private final HttpClientFactory httpClientFactory;
+ private final MsoUtils utils;
+ private final ExceptionUtil exceptionUtil;
+
public static final String PostServiceOrderRequestsTemplate =
"{\n" +
"\t\"externalId\": <externalId>,\n" +
@@ -85,7 +86,10 @@ class ExternalAPIUtil {
"\t} \n" +
"}"
- public ExternalAPIUtil() {
+ ExternalAPIUtil(HttpClientFactory httpClientFactory, MsoUtils utils, ExceptionUtil exceptionUtil) {
+ this.httpClientFactory = httpClientFactory
+ this.utils = utils
+ this.exceptionUtil = exceptionUtil
}
// public String getUri(DelegateExecution execution, resourceName) {
@@ -137,8 +141,7 @@ class ExternalAPIUtil {
msoLogger.debug( "Generated uuid is: " + uuid)
msoLogger.debug( "URL to be used is: " + url)
- URL Url = new URL(url)
- HttpClient client = new HttpClient(Url, MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)
+ HttpClient client = httpClientFactory.create(new URL(url), MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)
client.addBasicAuthHeader(execution.getVariable("URN_externalapi_auth"), execution.getVariable("URN_mso_msoKey"))
client.addAdditionalHeader("X-FromAppId", "MSO")
client.addAdditionalHeader(ONAPLogConstants.Headers.REQUEST_ID, uuid)
@@ -166,17 +169,15 @@ class ExternalAPIUtil {
* @return Response
*
*/
- public Response executeExternalAPIPostCall(DelegateExecution execution, String urlString, String payload){
+ public Response executeExternalAPIPostCall(DelegateExecution execution, String url, String payload){
msoLogger.debug( " ======== Started Execute ExternalAPI Post Process ======== ")
Response apiResponse = null
try{
String uuid = utils.getRequestID()
msoLogger.debug( "Generated uuid is: " + uuid)
- msoLogger.debug( "URL to be used is: " + urlString)
-
- URL url = new URL(urlString);
+ msoLogger.debug( "URL to be used is: " + url)
- HttpClient httpClient = new HttpClient(url, "application/json", TargetEntity.AAI)
+ HttpClient httpClient = httpClientFactory.create(new URL(url), MediaType.APPLICATION_JSON, TargetEntity.AAI)
httpClient.addBasicAuthHeader(execution.getVariable("URN_externalapi_auth"), execution.getVariable("URN_mso_msoKey"))
httpClient.addAdditionalHeader("X-FromAppId", "MSO")
httpClient.addAdditionalHeader("X-TransactionId", uuid)
diff --git a/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy
new file mode 100644
index 0000000000..e7f46464ee
--- /dev/null
+++ b/bpmn/MSOCommonBPMN/src/main/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilFactory.groovy
@@ -0,0 +1,29 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2018 Nokia.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.so.bpmn.common.scripts
+
+import org.onap.so.client.HttpClientFactory
+
+class ExternalAPIUtilFactory {
+
+ ExternalAPIUtil create() {
+ return new ExternalAPIUtil(new HttpClientFactory(), new MsoUtils(), new ExceptionUtil())
+ }
+}
diff --git a/bpmn/MSOCommonBPMN/src/main/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetup.java b/bpmn/MSOCommonBPMN/src/main/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetup.java
index c0f1c48b76..b1e5d0d208 100644
--- a/bpmn/MSOCommonBPMN/src/main/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetup.java
+++ b/bpmn/MSOCommonBPMN/src/main/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetup.java
@@ -115,7 +115,7 @@ public class BBInputSetup implements JavaDelegate {
private BBInputSetupMapperLayer mapperLayer;
@Autowired
- private CloudInfoFromAAI bbInputSetupHelper;
+ private CloudInfoFromAAI cloudInfoFromAAI;
@Autowired
private ExceptionBuilder exceptionUtil;
@@ -125,6 +125,10 @@ public class BBInputSetup implements JavaDelegate {
public BBInputSetupUtils getBbInputSetupUtils() {
return bbInputSetupUtils;
}
+
+ public void setCloudInfoFromAAI(CloudInfoFromAAI cloudInfoFromAAI) {
+ this.cloudInfoFromAAI = cloudInfoFromAAI;
+ }
public void setBbInputSetupUtils(BBInputSetupUtils bbInputSetupUtils) {
this.bbInputSetupUtils = bbInputSetupUtils;
@@ -987,7 +991,7 @@ public class BBInputSetup implements JavaDelegate {
ServiceInstance serviceInstance = gBB.getServiceInstance();
CloudRegion cloudRegion = null;
if(cloudConfiguration == null) {
- Optional<CloudRegion> cloudRegionOp = bbInputSetupHelper.getCloudInfoFromAAI(serviceInstance);
+ Optional<CloudRegion> cloudRegionOp = cloudInfoFromAAI.getCloudInfoFromAAI(serviceInstance);
if(cloudRegionOp.isPresent()) {
cloudRegion = cloudRegionOp.get();
}
@@ -1040,9 +1044,9 @@ public class BBInputSetup implements JavaDelegate {
vnfModelInfo.setModelCustomizationUuid(vnfModelCustomizationUUID);
this.mapCatalogVnf(vnf, vnfModelInfo, service);
lookupKeyMap.put(ResourceKey.GENERIC_VNF_ID, vnf.getVnfId());
- if (cloudConfiguration != null) {
- String volumeGroupCustomizationUUID = this.bbInputSetupUtils.getAAIVolumeGroup(cloudConfiguration.getCloudOwner(),
- cloudConfiguration.getLcpCloudRegionId(), volumeGroup.getVolumeGroupId())
+ if (cloudRegion != null) {
+ String volumeGroupCustomizationUUID = this.bbInputSetupUtils.getAAIVolumeGroup(cloudRegion.getCloudOwner(),
+ cloudRegion.getLcpCloudRegionId(), volumeGroup.getVolumeGroupId())
.getModelCustomizationId();
ModelInfo volumeGroupModelInfo = new ModelInfo();
volumeGroupModelInfo.setModelCustomizationId(volumeGroupCustomizationUUID);
diff --git a/bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy b/bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy
new file mode 100644
index 0000000000..5f428f1f9f
--- /dev/null
+++ b/bpmn/MSOCommonBPMN/src/test/groovy/org/onap/so/bpmn/common/scripts/ExternalAPIUtilTest.groovy
@@ -0,0 +1,199 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2018 Nokia.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.so.bpmn.common.scripts
+
+import org.assertj.core.api.AbstractAssert
+import org.camunda.bpm.engine.delegate.BpmnError
+import org.camunda.bpm.engine.delegate.DelegateExecution
+import org.junit.Test
+import org.onap.logging.ref.slf4j.ONAPLogConstants
+import org.onap.so.client.HttpClient
+import org.onap.so.client.HttpClientFactory
+import org.onap.so.utils.TargetEntity
+import org.springframework.http.HttpStatus
+
+import javax.ws.rs.core.MediaType
+import javax.ws.rs.core.Response
+
+import static org.assertj.core.api.Assertions.assertThat
+import static org.assertj.core.api.Assertions.catchThrowableOfType
+import static org.mockito.BDDMockito.given
+import static org.mockito.BDDMockito.then
+import static org.mockito.BDDMockito.willThrow
+import static org.mockito.Mockito.mock
+import static org.mockito.Mockito.times
+
+class ExternalAPIUtilTest {
+
+ private static final String URL = "http://someUrl"
+ private static final String UUID_STR = UUID.nameUUIDFromBytes("deterministic_uuid".getBytes())
+ private static final String BODY_PAYLOAD = "payload"
+
+ @Test
+ void executeExternalAPIGetCall_shouldPerformRestGetCall_withAuthorizationHeaderSet() {
+ // GIVEN
+ Response expectedResponse = createExpectedResponse(HttpStatus.ACCEPTED, BODY_PAYLOAD)
+ HttpClient httpClient = mock(HttpClient.class)
+ given(httpClient.get()).willReturn(expectedResponse)
+ HttpClientFactory httpClientFactory = mock(HttpClientFactory.class)
+ given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)).willReturn(httpClient)
+
+ // WHEN
+ ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), new ExceptionUtil())
+ Response apiResponse = externalAPIUtil.executeExternalAPIGetCall(createDelegateExecution(), URL)
+
+ // THEN
+ then(httpClient).should(times(1)).addBasicAuthHeader("value_externalapi_auth", "value_mso_msoKey")
+ then(httpClient).should(times(1)).addAdditionalHeader("X-FromAppId", "MSO")
+ then(httpClient).should(times(1)).addAdditionalHeader(ONAPLogConstants.Headers.REQUEST_ID, UUID_STR)
+ then(httpClient).should(times(1)).addAdditionalHeader("Accept", MediaType.APPLICATION_JSON)
+ ResponseAssert.assertThat(apiResponse)
+ .hasStatusCode(HttpStatus.ACCEPTED)
+ .hasBody(BODY_PAYLOAD)
+ }
+
+ @Test
+ void executeExternalAPIGetCall_shouldHandleExceptionsThrownByGetCall_andRethrowAsBpmnError() {
+ // GIVEN
+ HttpClient httpClient = mock(HttpClient.class)
+ willThrow(new RuntimeException("error occurred")).given(httpClient).get()
+ HttpClientFactory httpClientFactory = mock(HttpClientFactory.class)
+ given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)).willReturn(httpClient)
+ DelegateExecution delegateExecution = createDelegateExecution()
+ DummyExceptionUtil exceptionUtil = new DummyExceptionUtil()
+
+ // WHEN
+ ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), exceptionUtil)
+ BpmnError bpmnError = catchThrowableOfType({ -> externalAPIUtil.executeExternalAPIGetCall(delegateExecution, URL)
+ }, BpmnError.class)
+
+ // THEN
+ assertThat(exceptionUtil.getDelegateExecution()).isSameAs(delegateExecution)
+ assertThat(bpmnError.getMessage()).isEqualTo("error occurred")
+ assertThat(bpmnError.getErrorCode()).isEqualTo("9999")
+ }
+
+ @Test
+ void executeExternalAPIPostCall_shouldHandleExceptionsThrownByPostCall_andRethrowAsBpmnError() {
+ // GIVEN
+ HttpClient httpClient = mock(HttpClient.class)
+ willThrow(new RuntimeException("error occurred")).given(httpClient).post(BODY_PAYLOAD)
+ HttpClientFactory httpClientFactory = mock(HttpClientFactory.class)
+ given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.AAI)).willReturn(httpClient)
+ DelegateExecution delegateExecution = createDelegateExecution()
+ DummyExceptionUtil exceptionUtil = new DummyExceptionUtil()
+
+ // WHEN
+ ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), exceptionUtil)
+ BpmnError bpmnError = catchThrowableOfType({ ->
+ externalAPIUtil.executeExternalAPIPostCall(delegateExecution, URL, BODY_PAYLOAD)
+ }, BpmnError.class)
+
+ // THEN
+ assertThat(exceptionUtil.getDelegateExecution()).isSameAs(delegateExecution)
+ assertThat(bpmnError.getMessage()).isEqualTo("error occurred")
+ assertThat(bpmnError.getErrorCode()).isEqualTo("9999")
+ }
+
+ @Test
+ void executeExternalAPIPostCall_shouldPerformRestPostCall_withPayloadAndAuthorizationHeaderSet() {
+ // GIVEN
+ Response expectedResponse = createExpectedResponse(HttpStatus.ACCEPTED, BODY_PAYLOAD)
+ HttpClient httpClient = mock(HttpClient.class)
+ given(httpClient.post(BODY_PAYLOAD)).willReturn(expectedResponse)
+ HttpClientFactory httpClientFactory = mock(HttpClientFactory.class)
+ given(httpClientFactory.create(new URL(URL), MediaType.APPLICATION_JSON, TargetEntity.AAI)).willReturn(httpClient)
+
+ // WHEN
+ ExternalAPIUtil externalAPIUtil = new ExternalAPIUtil(httpClientFactory, new DummyMsoUtils(UUID_STR), new ExceptionUtil())
+ Response apiResponse = externalAPIUtil.executeExternalAPIPostCall(createDelegateExecution(), URL, BODY_PAYLOAD)
+
+ // THEN
+ then(httpClient).should(times(1)).addBasicAuthHeader("value_externalapi_auth", "value_mso_msoKey")
+ then(httpClient).should(times(1)).addAdditionalHeader("X-FromAppId", "MSO")
+ then(httpClient).should(times(1)).addAdditionalHeader("X-TransactionId", UUID_STR)
+ ResponseAssert.assertThat(apiResponse)
+ .hasStatusCode(HttpStatus.ACCEPTED)
+ .hasBody(BODY_PAYLOAD)
+ }
+
+ private Response createExpectedResponse(HttpStatus httpStatus, String body) {
+ Response expectedResponse = mock(Response.class)
+ given(expectedResponse.getStatus()).willReturn(httpStatus.value())
+ given(expectedResponse.getEntity()).willReturn(body)
+ return expectedResponse
+ }
+
+ private DelegateExecution createDelegateExecution() {
+ DelegateExecution delegateExecution = mock(DelegateExecution.class)
+ given(delegateExecution.getVariable("URN_externalapi_auth")).willReturn("value_externalapi_auth")
+ given(delegateExecution.getVariable("URN_mso_msoKey")).willReturn("value_mso_msoKey")
+ return delegateExecution
+ }
+
+ private static class ResponseAssert extends AbstractAssert<ResponseAssert, Response> {
+
+ ResponseAssert(Response response) {
+ super(response, ResponseAssert.class)
+ }
+
+ static ResponseAssert assertThat(Response response) {
+ return new ResponseAssert(response)
+ }
+
+ ResponseAssert hasStatusCode(HttpStatus httpStatus) {
+ assertThat(actual.getStatus()).isEqualTo(httpStatus.value())
+ return this
+ }
+
+ ResponseAssert hasBody(String responseBody) {
+ assertThat(actual.getEntity()).isEqualTo(responseBody)
+ return this
+ }
+ }
+
+ private static class DummyMsoUtils extends MsoUtils {
+
+ private final String uuid
+
+ DummyMsoUtils(String uuid) {
+ this.uuid = uuid
+ }
+
+ String getRequestID() {
+ return uuid
+ }
+ }
+
+ private static class DummyExceptionUtil extends ExceptionUtil {
+
+ private DelegateExecution delegateExecution
+
+ @Override
+ void buildAndThrowWorkflowException(DelegateExecution delegateExecution, int errorCode, String errorMessage) {
+ this.delegateExecution = delegateExecution
+ throw new BpmnError(String.valueOf(errorCode), errorMessage)
+ }
+
+ DelegateExecution getDelegateExecution() {
+ return delegateExecution
+ }
+ }
+} \ No newline at end of file
diff --git a/bpmn/MSOCommonBPMN/src/test/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetupTest.java b/bpmn/MSOCommonBPMN/src/test/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetupTest.java
index 8399fe15a3..1654aa06de 100644
--- a/bpmn/MSOCommonBPMN/src/test/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetupTest.java
+++ b/bpmn/MSOCommonBPMN/src/test/java/org/onap/so/bpmn/servicedecomposition/tasks/BBInputSetupTest.java
@@ -40,6 +40,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.junit.Before;
@@ -130,6 +131,9 @@ public class BBInputSetupTest {
@Mock
private BBInputSetupUtils SPY_bbInputSetupUtils;
+ @Mock
+ private CloudInfoFromAAI SPY_cloudInfoFromAAI;
+
@Spy
private BBInputSetupMapperLayer bbInputSetupMapperLayer;
@@ -137,6 +141,7 @@ public class BBInputSetupTest {
public void setup(){
SPY_bbInputSetup.setBbInputSetupUtils(SPY_bbInputSetupUtils);
SPY_bbInputSetup.setMapperLayer(bbInputSetupMapperLayer);
+ SPY_bbInputSetup.setCloudInfoFromAAI(SPY_cloudInfoFromAAI);
}
@Test
@@ -2270,19 +2275,22 @@ public class BBInputSetupTest {
verify(SPY_bbInputSetup, times(1)).mapCatalogVfModule(any(VfModule.class), any(ModelInfo.class),
any(Service.class), any(String.class));
- org.onap.aai.domain.yang.CloudRegion aaiCloudRegion = Mockito.mock(org.onap.aai.domain.yang.CloudRegion.class);
+ CloudRegion cloudRegion = new CloudRegion();
+ cloudRegion.setLcpCloudRegionId("cloudRegionId");
+ cloudRegion.setCloudOwner("CloudOwner");
+ doReturn(Optional.of(cloudRegion)).when(SPY_cloudInfoFromAAI).getCloudInfoFromAAI(gBB.getServiceInstance());
VolumeGroup volumeGroup = new VolumeGroup();
volumeGroup.setVolumeGroupId("volumeGroupId");
gBB.getServiceInstance().getVnfs().get(0).getVolumeGroups().add(volumeGroup);
org.onap.aai.domain.yang.VolumeGroup aaiVolumeGroup = new org.onap.aai.domain.yang.VolumeGroup();
aaiVolumeGroup.setModelCustomizationId("modelCustId");
- doReturn(aaiVolumeGroup).when(SPY_bbInputSetupUtils).getAAIVolumeGroup(Defaults.CLOUD_OWNER.toString(),
- cloudConfiguration.getLcpCloudRegionId(), volumeGroup.getVolumeGroupId());
+ doReturn(aaiVolumeGroup).when(SPY_bbInputSetupUtils).getAAIVolumeGroup(cloudRegion.getCloudOwner(),
+ cloudRegion.getLcpCloudRegionId(), volumeGroup.getVolumeGroupId());
executeBB.getBuildingBlock().setBpmnFlowName("UnassignVolumeGroupBB");
executeBB.getBuildingBlock().setKey("72d9d1cd-f46d-447a-abdb-451d6fb05fa8");
SPY_bbInputSetup.getGBBMacroExistingService(executeBB, lookupKeyMap,
- executeBB.getBuildingBlock().getBpmnFlowName(), gBB, service, requestAction, cloudConfiguration);
+ executeBB.getBuildingBlock().getBpmnFlowName(), gBB, service, requestAction, null);
verify(SPY_bbInputSetup, times(3)).mapCatalogVnf(any(GenericVnf.class), any(ModelInfo.class),
any(Service.class));
verify(SPY_bbInputSetup, times(1)).mapCatalogVolumeGroup(isA(VolumeGroup.class), isA(ModelInfo.class),