| /******************************************************************************* |
| * Copyright (C) 2021 the Eclipse BaSyx Authors |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| ******************************************************************************/ |
| package org.eclipse.basyx.testsuite.regression.submodel.restapi; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; |
| import org.eclipse.basyx.submodel.metamodel.facade.SubmodelElementMapCollectionConverter; |
| import org.eclipse.basyx.submodel.metamodel.map.SubModel; |
| import org.eclipse.basyx.submodel.metamodel.map.qualifier.Identifiable; |
| import org.eclipse.basyx.submodel.metamodel.map.qualifier.Referable; |
| import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.property.Property; |
| import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.property.valuetypedef.PropertyValueTypeDefHelper; |
| import org.eclipse.basyx.submodel.restapi.MultiSubmodelElementProvider; |
| import org.eclipse.basyx.submodel.restapi.OperationProvider; |
| import org.eclipse.basyx.submodel.restapi.SubModelProvider; |
| import org.eclipse.basyx.submodel.restapi.operation.CallbackResponse; |
| import org.eclipse.basyx.submodel.restapi.operation.ExecutionState; |
| import org.eclipse.basyx.submodel.restapi.operation.InvocationResponse; |
| import org.eclipse.basyx.testsuite.regression.submodel.metamodel.map.submodelelement.operation.AsyncOperationHelper; |
| import org.eclipse.basyx.testsuite.regression.vab.protocol.http.TestsuiteDirectory; |
| import org.eclipse.basyx.vab.exception.provider.MalformedRequestException; |
| import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; |
| import org.eclipse.basyx.vab.manager.VABConnectionManager; |
| import org.eclipse.basyx.vab.modelprovider.VABElementProxy; |
| import org.eclipse.basyx.vab.modelprovider.VABPathTools; |
| import org.eclipse.basyx.vab.modelprovider.api.IModelProvider; |
| import org.eclipse.basyx.vab.protocol.api.ConnectorProvider; |
| import org.junit.Test; |
| |
| public class SubModelProviderTest { |
| private VABConnectionManager connManager; |
| protected static final String submodelAddr = "urn:fhg:es.iese:aas:1:1:submodel"; |
| |
| protected VABConnectionManager getConnectionManager() { |
| if (connManager == null) { |
| connManager = new VABConnectionManager(new TestsuiteDirectory(), new ConnectorProvider() { |
| @Override |
| protected IModelProvider createProvider(String addr) { |
| // Simple submodel for testing specific mappings for submodels |
| return new SubModelProvider(new SimpleAASSubmodel("mySubmodelId")); |
| } |
| }); |
| } |
| return connManager; |
| } |
| |
| /** |
| * Tests accessing different paths that should be supported |
| * @throws Exception |
| */ |
| @Test |
| public void testPathsRaw() throws Exception { |
| SubModelProvider provider = new SubModelProvider(new SimpleAASSubmodel("mySubmodelId")); |
| provider.getModelPropertyValue("/submodel"); |
| provider.getModelPropertyValue("/submodel/"); |
| |
| try { |
| provider.getModelPropertyValue("invalid"); |
| fail(); |
| } catch (Exception e) { |
| |
| } |
| } |
| |
| /** |
| * Test creating single property |
| */ |
| @Test |
| public void testCreateProperty() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Create element |
| Property prop = new Property(500); |
| prop.setIdShort("newProperty"); |
| submodelElement.setModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/newProperty", prop); |
| |
| // Read back value |
| Integer result = (Integer) submodelElement |
| .getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/newProperty/value"); |
| assertEquals(500, result.intValue()); |
| } |
| |
| /** |
| * Test creating single property |
| */ |
| @Test |
| public void testCreatePropertyInCollection() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Create element |
| Property prop = new Property(500); |
| prop.setIdShort("newProperty"); |
| submodelElement.setModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/containerRoot/newProperty", prop); |
| |
| // Read back value |
| Integer result = (Integer) submodelElement |
| .getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/containerRoot/newProperty/value"); |
| assertEquals(500, result.intValue()); |
| |
| |
| submodelElement.setModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/containerRoot/container/newProperty", prop); |
| |
| // Read back value |
| result = (Integer) submodelElement |
| .getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/containerRoot/container/newProperty/value"); |
| assertEquals(500, result.intValue()); |
| } |
| |
| /** |
| * Test reading single property |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testReadProperty() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Read list of properties |
| Object result = submodelElement.getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + ""); |
| Collection<Map<String, Object>> propertySet = (Collection<Map<String, Object>>) result; |
| Map<String, Object> property = propertySet.stream().filter(elem -> elem.get(Identifiable.IDSHORT).equals("integerProperty")).findFirst().get(); |
| assertEquals(123, property.get(Property.VALUE)); |
| |
| // Read whole property |
| result = submodelElement.getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/integerProperty"); |
| property = (Map<String, Object>) result; |
| assertEquals(123, property.get(Property.VALUE)); |
| |
| // Read idShort |
| result = submodelElement.getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/stringProperty"); |
| property = (Map<String, Object>) result; |
| assertEquals("stringProperty", property.get(Identifiable.IDSHORT)); |
| |
| // Read single value |
| String resString = (String) submodelElement |
| .getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/stringProperty/value"); |
| assertEquals("Test", resString); |
| |
| // Read null value |
| Object resObject = submodelElement |
| .getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/nullProperty/value"); |
| assertEquals(null, resObject); |
| |
| // Read container property |
| Collection<Object> resSet = (Collection<Object>) submodelElement |
| .getModelPropertyValue("/submodel/submodelElements/containerRoot/value"); |
| assertEquals(1, resSet.size()); |
| |
| // Get Collection from root-Collection |
| Map<String, Object> container = (Map<String, Object>) resSet.iterator().next(); |
| |
| assertEquals("container", container.get(Referable.IDSHORT)); |
| assertTrue(container.get(Property.VALUE) instanceof Collection<?>); |
| |
| // Get Value of nested Collection |
| Map<String, Object> containerValue = SubmodelElementMapCollectionConverter.convertCollectionToIDMap(container.get(Property.VALUE)); |
| |
| // Check content of nested Collection |
| assertTrue(containerValue.containsKey("operationId")); |
| assertTrue(containerValue.containsKey("integerProperty")); |
| assertEquals(123, ((Property) containerValue.get("integerProperty")).get()); |
| |
| // Read nested property |
| String pathToNestedContainer = "/submodel/submodelElements/containerRoot/container"; |
| String pathToNestedProperty = pathToNestedContainer + "/integerProperty/"; |
| result = submodelElement.getModelPropertyValue(pathToNestedProperty); |
| property = (Map<String, Object>) result; |
| assertEquals(123, property.get(Property.VALUE)); |
| } |
| |
| /** |
| * Test updating single property |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testUpdateProperty() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Update element |
| submodelElement.setModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/integerProperty/value", 3); |
| |
| // Check result |
| Map<String, Object> result = (Map<String, Object>) submodelElement |
| .getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/integerProperty"); |
| assertEquals(3, result.get(Property.VALUE)); |
| } |
| |
| /** |
| * Test updating a SubmodelElementCollection |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testUpdateSmElementCollection() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| Collection<ISubmodelElement> smElements = new ArrayList<>(); |
| Property newProperty = new Property("propValue"); |
| newProperty.setIdShort("propIdShort"); |
| smElements.add(newProperty); |
| |
| // update value of smElemCollection |
| String path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, "containerRoot"); |
| submodelElement.setModelPropertyValue(path + "/value", smElements); |
| |
| // read back the collection |
| Map<String, Object> map = (Map<String, Object>) submodelElement |
| .getModelPropertyValue(path); |
| |
| assertTrue(map.get(Property.VALUE) instanceof Collection<?>); |
| |
| Collection<Map<String, Object>> elements = (Collection<Map<String, Object>>) map.get(Property.VALUE); |
| assertEquals(1, elements.size()); |
| |
| Iterator<Map<String, Object>> i = elements.iterator(); |
| |
| assertEquals("propIdShort", i.next().get(Referable.IDSHORT)); |
| } |
| |
| /** |
| * Test updating a Property inside a SubmodelElementCollection |
| */ |
| @Test |
| public void testUpdateElementInSmElementCollection() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| String path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, |
| "containerRoot", "container", "integerProperty", "value"); |
| |
| Integer value = (Integer) submodelElement.getModelPropertyValue(path); |
| assertEquals(123, value.intValue()); |
| |
| submodelElement.setModelPropertyValue(path, 321); |
| |
| value = (Integer) submodelElement.getModelPropertyValue(path); |
| assertEquals(321, value.intValue()); |
| } |
| |
| /** |
| * Test reading a single operation |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testReadSingleOperation() { |
| VABElementProxy submodel = getConnectionManager().connectToVABElement(submodelAddr); |
| Map<String, Object> operation = (Map<String, Object>) submodel.getModelPropertyValue("/submodel/submodelElements/simple"); |
| assertEquals("simple", operation.get(Identifiable.IDSHORT)); |
| |
| try { |
| submodel.getModelPropertyValue("/submodel/submodelElements/simple/value"); |
| fail(); |
| } catch (MalformedRequestException e) { |
| // An Operation has no value |
| } |
| } |
| |
| /** |
| * Checks if the submodel elements in a read submodel are within a collection |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testReadSubmodelCheckElementsInCollection() { |
| VABElementProxy submodel = getConnectionManager().connectToVABElement(submodelAddr); |
| Map<String, Object> smMap = (Map<String, Object>) submodel.getModelPropertyValue("/submodel"); |
| Object o = smMap.get(SubModel.SUBMODELELEMENT); |
| assertTrue(o instanceof Collection<?>); |
| } |
| |
| /** |
| * Test reading all submodel elements of the submodel |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testReadSubModelElements() { |
| VABElementProxy submodel = getConnectionManager().connectToVABElement(submodelAddr); |
| Collection<Map<String, Object>> set = (Collection<Map<String, Object>>) submodel |
| .getModelPropertyValue("/submodel/submodelElements"); |
| assertEquals(8, set.size()); |
| } |
| |
| /** |
| * Test deleting a single property |
| */ |
| @Test |
| public void testDeleteProperty() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Delete property |
| submodelElement.deleteValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/integerProperty"); |
| |
| // Test, if it has been deleted |
| try { |
| submodelElement.getModelPropertyValue("/submodel/" + MultiSubmodelElementProvider.ELEMENTS + "/integerProperty"); |
| fail(); |
| } catch (ResourceNotFoundException e) {} |
| } |
| |
| /** |
| * Test deleting a single operation |
| */ |
| @Test |
| public void testDeleteOperation() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Delete operation |
| submodelElement.deleteValue("/submodel/submodelElements/simple"); |
| |
| // Test, if it has been deleted |
| try { |
| submodelElement.getModelPropertyValue("/submodel/submodelElements/simple"); |
| fail(); |
| } catch (ResourceNotFoundException e) {} |
| } |
| |
| /** |
| * Test deleting a single property from a SubmodelElementCollection |
| */ |
| @Test |
| public void testDeletePropertyFromCollection() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| String path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, |
| "containerRoot", "container", "integerProperty"); |
| |
| assertNotNull(submodelElement.getModelPropertyValue(path)); |
| |
| // Delete property |
| submodelElement.deleteValue(path); |
| |
| // Test if parent Collection is still there |
| assertNotNull(submodelElement.getModelPropertyValue(VABPathTools.getParentPath(path))); |
| |
| // Test, if it has been deleted |
| try { |
| submodelElement.getModelPropertyValue(path); |
| fail(); |
| } catch (ResourceNotFoundException e) {} |
| |
| // Test delete the Collection "container" |
| path = VABPathTools.getParentPath(path); |
| |
| // Delete property |
| submodelElement.deleteValue(path); |
| |
| // Test if parent Collection is still there |
| assertNotNull(submodelElement.getModelPropertyValue(VABPathTools.getParentPath(path))); |
| |
| // Test, if it has been deleted |
| try { |
| submodelElement.getModelPropertyValue(path); |
| fail(); |
| } catch (ResourceNotFoundException e) {} |
| } |
| |
| /** |
| * Test invoking an operation |
| */ |
| @Test |
| public void testInvokeOperation() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| // Wrap parameters before invoking add-operation |
| Map<String, Object> param1 = wrapParameter("FirstNumber", 5); |
| Map<String, Object> param2 = wrapParameter("SecondNumber", 2); |
| |
| // Invoke operation with wrapped parameters and check result |
| Object result = submodelElement.invokeOperation("/submodel/submodelElements/complex/invoke", param1, param2); |
| assertEquals(3, result); |
| |
| // Invoke operation on parent element |
| result = submodelElement.invokeOperation("/submodel/submodelElements/simple/invoke"); |
| assertTrue((boolean) result); |
| } |
| |
| /** |
| * Test invoking an operation from within a SubmodelElementCollection |
| */ |
| @Test |
| public void testInvokeOperationInCollection() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| |
| String path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, |
| "containerRoot", "container", "operationId", "invoke"); |
| |
| Object result = submodelElement.invokeOperation(path); |
| assertEquals(123, result); |
| } |
| |
| /** |
| * Test getting /values of the Submodel |
| */ |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testGetValues() { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| Map<String, Object> values = (Map<String, Object>) submodelElement.getModelPropertyValue("submodel/" + SubModelProvider.VALUES); |
| |
| assertEquals(4, values.size()); |
| |
| // Check if all expected Values are present |
| assertTrue(values.containsKey("containerRoot")); |
| Map<String, Object> collection1 = (Map<String, Object>) values.get("containerRoot"); |
| |
| assertTrue(collection1.containsKey("container")); |
| Map<String, Object> collection2 = (Map<String, Object>) collection1.get("container"); |
| |
| // Check the Value in /containerRoot/container/integerProperty |
| assertEquals(123, collection2.get("integerProperty")); |
| |
| assertEquals("Test", values.get("stringProperty")); |
| assertEquals(123, values.get("integerProperty")); |
| assertEquals(null, values.get("nullProperty")); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testInvokeAsync() throws Exception { |
| VABElementProxy elementProxy = getConnectionManager().connectToVABElement(submodelAddr); |
| AsyncOperationHelper helper = new AsyncOperationHelper(); |
| |
| String path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, AsyncOperationHelper.ASYNC_OP_ID); |
| elementProxy.setModelPropertyValue(path, helper.getAsyncOperation()); |
| |
| // Wrap parameters before invoking add-operation |
| Map<String, Object> param1 = wrapParameter("FirstNumber", 5); |
| Map<String, Object> param2 = wrapParameter("SecondNumber", 2); |
| |
| path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, AsyncOperationHelper.ASYNC_OP_ID, "invoke?async=true"); |
| |
| CallbackResponse response = CallbackResponse.createAsFacade((Map<String, Object>) elementProxy.invokeOperation(path, param1, param2)); |
| String requestId = response.getRequestId(); |
| |
| String listPath = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, AsyncOperationHelper.ASYNC_OP_ID, OperationProvider.INVOCATION_LIST); |
| |
| // Try correct operationId, wrong requestId |
| try { |
| elementProxy.getModelPropertyValue(VABPathTools.append(listPath, "nonexistent")); |
| fail(); |
| } catch (ResourceNotFoundException e) { |
| } |
| |
| // Try wrong operationId, correct requestId |
| try { |
| elementProxy.getModelPropertyValue("/submodel/submodelElements/simple/invocationList/" + requestId); |
| fail(); |
| } catch (ResourceNotFoundException e) { |
| } |
| |
| String requestPath = VABPathTools.append(listPath, requestId); |
| |
| // Check that it has not finished yet |
| InvocationResponse result = (InvocationResponse) elementProxy.getModelPropertyValue(requestPath); |
| assertEquals(ExecutionState.INITIATED, result.getExecutionState()); |
| |
| helper.releaseWaitingOperation(); |
| |
| result = (InvocationResponse) elementProxy.getModelPropertyValue(requestPath); |
| assertEquals(7, result.getFirstOutput()); |
| |
| // Check if the async-invocation is deleted after retrieving its result |
| try { |
| elementProxy.getModelPropertyValue(requestPath); |
| fail(); |
| } catch (ResourceNotFoundException e) { |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Test |
| public void testInvokeAsyncException() throws Exception { |
| VABElementProxy submodelElement = getConnectionManager().connectToVABElement(submodelAddr); |
| AsyncOperationHelper helper = new AsyncOperationHelper(); |
| |
| String path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, AsyncOperationHelper.ASYNC_EXCEPTION_OP_ID); |
| submodelElement.setModelPropertyValue(path, helper.getAsyncExceptionOperation()); |
| |
| path = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, |
| AsyncOperationHelper.ASYNC_EXCEPTION_OP_ID, "invoke?async=true"); |
| |
| CallbackResponse response = (CallbackResponse) submodelElement.invokeOperation(path); |
| String requestId = response.getRequestId(); |
| |
| String requestPath = VABPathTools.concatenatePaths("submodel", MultiSubmodelElementProvider.ELEMENTS, |
| AsyncOperationHelper.ASYNC_EXCEPTION_OP_ID, OperationProvider.INVOCATION_LIST, requestId); |
| |
| // Check that it has not finished yet |
| InvocationResponse invResp = InvocationResponse |
| .createAsFacade((Map<String, Object>) submodelElement.getModelPropertyValue(requestPath)); |
| assertNotEquals(ExecutionState.COMPLETED, invResp.getExecutionState()); |
| assertNotEquals(ExecutionState.FAILED, invResp.getExecutionState()); |
| |
| helper.releaseWaitingOperation(); |
| |
| |
| invResp = InvocationResponse |
| .createAsFacade((Map<String, Object>) submodelElement.getModelPropertyValue(requestPath)); |
| assertEquals(ExecutionState.FAILED, invResp.getExecutionState()); |
| |
| // Check if the async-invocation is deleted after retrieving its result |
| try { |
| submodelElement.getModelPropertyValue(requestPath); |
| fail(); |
| } catch (ResourceNotFoundException e) { |
| } |
| } |
| |
| private Map<String, Object> wrapParameter(String name, Object value) { |
| Map<String, Object> param = new HashMap<>(); |
| param.put(Identifiable.IDSHORT, name); |
| param.put(Property.VALUE, value); |
| param.put(Property.VALUETYPE, PropertyValueTypeDefHelper.getType(value).toString()); |
| return param; |
| } |
| } |