/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI 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.openengsb.core.services.internal; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import org.aopalliance.intercept.MethodInterceptor; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.openengsb.core.api.Connector; import org.openengsb.core.api.ConnectorInstanceFactory; import org.openengsb.core.api.ConnectorValidationFailedException; import org.openengsb.core.api.Constants; import org.openengsb.core.api.Domain; import org.openengsb.core.api.DomainProvider; import org.openengsb.core.api.LinkingSupport; import org.openengsb.core.api.OsgiServiceNotAvailableException; import org.openengsb.core.api.model.ConnectorDescription; import org.openengsb.core.api.model.ModelDescription; import org.openengsb.core.api.persistence.ConfigPersistenceService; import org.openengsb.core.api.xlink.model.ModelViewMapping; import org.openengsb.core.api.xlink.model.XLinkConnectorRegistration; import org.openengsb.core.api.xlink.model.XLinkConnectorView; import org.openengsb.core.api.xlink.model.XLinkConstants; import org.openengsb.core.api.xlink.service.XLinkConnectorManager; import org.openengsb.core.common.SecurityAttributeProviderImpl; import org.openengsb.core.ekb.api.TransformationEngine; import org.openengsb.core.persistence.internal.DefaultConfigPersistenceService; import org.openengsb.core.services.internal.model.NullConnector; import org.openengsb.core.services.internal.model.NullModel; import org.openengsb.core.services.internal.xlink.ExampleObjectOrientedModel; import org.openengsb.core.test.AbstractOsgiMockServiceTest; import org.openengsb.core.test.DummyConfigPersistenceService; import org.openengsb.core.test.DummyModel; import org.openengsb.core.test.NullDomain; import org.openengsb.core.test.NullDomainImpl; import org.openengsb.core.util.DefaultOsgiUtilsService; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; public class ConnectorManagerTest extends AbstractOsgiMockServiceTest { private DefaultOsgiUtilsService serviceUtils; private DefaultOsgiUtilsService mockedServiceUtils; private XLinkConnectorManager connectorManager; private ConnectorRegistrationManager serviceRegistrationManagerImpl; private ConnectorInstanceFactory factory; private DefaultConfigPersistenceService configPersistence; private LinkingSupport testConnector; private TransformationEngine transformationEngine; @Before public void setUp() throws Exception { registerMockedDomainProvider(); registerMockedFactory(); registerConfigPersistence(); transformationEngine = mock(TransformationEngine.class); when(transformationEngine .performTransformation(any(ModelDescription.class), any(ModelDescription.class), any())) .thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return invocation.getArguments()[2]; } }); MethodInterceptor securityInterceptor = new ForwardMethodInterceptor(); serviceRegistrationManagerImpl = new ConnectorRegistrationManager(bundleContext, transformationEngine, securityInterceptor, new SecurityAttributeProviderImpl()); serviceUtils = new DefaultOsgiUtilsService(bundleContext); mockedServiceUtils = mock(DefaultOsgiUtilsService.class); mockedServiceUtils = mock(DefaultOsgiUtilsService.class); testConnector = mock(LinkingSupport.class); LinkingSupport testConnector2 = mock(LinkingSupport.class); Domain testConnector3 = mock(Domain.class); when(mockedServiceUtils.getService("(service.pid=test+test+test)", 100L)).thenReturn(testConnector); when(mockedServiceUtils.getService("(service.pid=test2+test2+test2)", 100L)).thenReturn(testConnector2); when(mockedServiceUtils.getService("(service.pid=test3+test3+test3)", 100L)).thenReturn(null); when(mockedServiceUtils.getService("(service.pid=test4+test4+test4)", 100L)).thenReturn(testConnector3); createServiceManager(); } private void registerConfigPersistence() { DummyConfigPersistenceService<String> backend = new DummyConfigPersistenceService<String>(); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(Constants.CONFIGURATION_ID, Constants.CONFIG_CONNECTOR); props.put(Constants.BACKEND_ID, "dummy"); configPersistence = new DefaultConfigPersistenceService(backend); registerService(configPersistence, props, ConfigPersistenceService.class); } private void createServiceManager() { XLinkConnectorManagerImpl serviceManagerImpl = new XLinkConnectorManagerImpl(); serviceManagerImpl.setRegistrationManager(serviceRegistrationManagerImpl); serviceManagerImpl.setConfigPersistence(configPersistence); serviceManagerImpl.setxLinkBaseUrl("http://localhost/openXLink"); serviceManagerImpl.setUtilsService(mockedServiceUtils); connectorManager = serviceManagerImpl; } @Test public void testCreateService_shouldCreateInstanceWithFactory() throws Exception { Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); connectorManager.create(connectorDescription); serviceUtils.getService("(foo=bar)", 100L); } @Test public void testCreateService_shouldExist() throws Exception { Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); String id = connectorManager.create(connectorDescription); boolean exists = connectorManager.connectorExists(id); assertTrue("Service doesn't exist after creation", exists); } @Test public void testUpdateService_shouldUpdateInstance() throws Exception { Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); String uuid = connectorManager.create(connectorDescription); connectorDescription.getProperties().put("foo", "42"); connectorDescription.getAttributes().put("answer", "43"); connectorManager.update(uuid, connectorDescription); } @SuppressWarnings("unchecked") @Test public void testCreateServiceWithInvalidAttributes_shouldFail() throws Exception { Map<String, String> errorMessages = new HashMap<String, String>(); errorMessages.put("all", "because I don't like you"); when(factory.getValidationErrors(anyMap())).thenReturn(errorMessages); Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); try { connectorManager.create(connectorDescription); fail("Exception expected"); } catch (RuntimeException e) { assertThat(((ConnectorValidationFailedException) e.getCause()).getErrorMessages(), is(errorMessages)); } try { serviceUtils.getService(NullDomain.class, 100L); fail("service is available, but shouldn't be"); } catch (OsgiServiceNotAvailableException e) { // expected. No service should be available because the attributes were invalid } } @SuppressWarnings("unchecked") @Test public void testForceCreateServiceWithInvalidAttributes_shouldCreateConnector() throws Exception { Map<String, String> errorMessages = new HashMap<String, String>(); errorMessages.put("all", "because I don't like you"); when(factory.getValidationErrors(anyMap())).thenReturn(errorMessages); Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); connectorManager.forceCreate(connectorDescription); try { serviceUtils.getService("(foo=bar)", 100L); } catch (OsgiServiceNotAvailableException e) { fail("service should be available because validation should have been skipped"); } } @SuppressWarnings("unchecked") @Test public void testUpdateServiceWithInvalidAttributes_shouldLeaveServiceUnchanged() throws Exception { Map<String, String> errorMessages = new HashMap<String, String>(); errorMessages.put("all", "because I don't like you"); when(factory.getValidationErrors(any(Connector.class), anyMap())).thenReturn(errorMessages); Map<String, String> attributes = new HashMap<String, String>(); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); String connectorId = connectorManager.create(connectorDescription); serviceUtils.getService("(foo=bar)", 1L); connectorDescription.getProperties().put("foo", "42"); try { connectorManager.update(connectorId, connectorDescription); fail("Exception expected"); } catch (RuntimeException e) { assertThat(((ConnectorValidationFailedException) e.getCause()).getErrorMessages(), is(errorMessages)); } try { serviceUtils.getService("(foo=bar)", 1L); } catch (OsgiServiceNotAvailableException e) { fail("Service is not available with the old attributes"); } try { serviceUtils.getService("(foo=42)", 1L); fail("Service should not be available with the new properties, but it is"); } catch (OsgiServiceNotAvailableException e) { // expected. The properties should not have been updated, so no service is available. } } @SuppressWarnings("unchecked") @Test public void testForceUpdateServiceWithInvalidAttributes_shouldUpdateService() throws Exception { Map<String, String> errorMessages = new HashMap<String, String>(); errorMessages.put("all", "because I don't like you"); when(factory.getValidationErrors(any(Connector.class), anyMap())).thenReturn(errorMessages); Map<String, String> attributes = new HashMap<String, String>(); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); String connectorId = connectorManager.create(connectorDescription); serviceUtils.getService("(foo=bar)", 1L); connectorDescription.getProperties().put("foo", "42"); connectorManager.forceUpdate(connectorId, connectorDescription); try { serviceUtils.getService("(foo=bar)", 1L); fail("Service is only available with the old attributes"); } catch (OsgiServiceNotAvailableException e) { // expected. The attributes have been overwritten } try { serviceUtils.getService("(foo=42)", 1L); } catch (OsgiServiceNotAvailableException e) { fail("Service should be available with the new properties, since validation should have been skipped"); } } @Test public void testDeleteService_shouldNotBeAvailableAnymore() throws Exception { Map<String, String> attributes = new HashMap<String, String>(); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); String connectorId = connectorManager.create(connectorDescription); connectorManager.delete(connectorId); try { serviceUtils.getService("(foo=bar)", 100L); fail("service should not be available anymore"); } catch (OsgiServiceNotAvailableException e) { // expected } try { connectorManager.getAttributeValues(connectorId); fail("service was still in persistence after deletion"); } catch (IllegalArgumentException e) { // expected } } @Test public void testDeleteService_shouldNotExistAnymore() throws Exception { Map<String, String> attributes = new HashMap<String, String>(); Map<String, Object> properties = new Hashtable<String, Object>(); boolean connectorExists; properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); String connectorId = connectorManager.create(connectorDescription); connectorManager.delete(connectorId); connectorExists = connectorManager.connectorExists(connectorId); assertFalse("service was still existing after deletion", connectorExists); } @Test public void testGetXLinkRegistration_isEmptyOnInitial() { String hostId = "127.0.0.1"; assertTrue(connectorManager.getXLinkRegistrations(hostId).isEmpty()); } @Test public void testGetXLinkRegistration_returnsConnectedRegistration() { String connectorId = "test+test+test"; String hostId = "127.0.0.1"; String toolName = "myTool"; ModelViewMapping[] modelViewMappings = createModelViewsMappings(toolName); connectorManager.registerWithXLink(connectorId, hostId, toolName, modelViewMappings); List<XLinkConnectorRegistration> registrations = connectorManager.getXLinkRegistrations(hostId); assertEquals(1, registrations.size()); assertThat(registrations.get(0).getHostId(), is(hostId)); assertThat(registrations.get(0).getConnectorId(), is(connectorId)); assertThat(registrations.get(0).getToolName(), is(toolName)); } @Test public void testUnregisterFromXLink_OnMissingRegistration_NoFail() { String connectorId = "test+test+test"; connectorManager.unregisterFromXLink(connectorId); } @Test public void testUnregisterFromXLink_isEmptyAfterDisconnect() { String connectorId = "test+test+test"; String hostId = "127.0.0.1"; String toolName = "myTool"; ModelViewMapping[] modelViewMappings = createModelViewsMappings(toolName); connectorManager.registerWithXLink(connectorId, hostId, toolName, modelViewMappings); connectorManager.unregisterFromXLink(connectorId); assertTrue(connectorManager.getXLinkRegistrations(hostId).isEmpty()); } // @Test(expected = DomainNotLinkableException.class) // public void testConnectorValidation_connectorIsNullAndFails() { // String connectorId = "test3+test3+test3"; // String hostId = "127.0.0.1"; // String toolName = "myTool"; // ModelViewMapping[] modelViewMappings // = createModelViewsMappings(toolName); // connectorManager.registerForXLink(connectorId, hostId, toolName, modelViewMappings); // } // // @Test(expected = DomainNotLinkableException.class) // public void testConnectorValidation_connectorIsNotLinkableAndFails() { // String connectorId = "test4+test4+test4"; // String hostId = "127.0.0.1"; // String toolName = "myTool"; // ModelViewMapping[] modelViewMappings // = createModelViewsMappings(toolName); // connectorManager.registerForXLink(connectorId, hostId, toolName, modelViewMappings); // } private ModelViewMapping[] createModelViewsMappings(String toolName) { String viewId1 = "exampleViewId_1"; String viewId2 = "exampleViewId_2"; ModelViewMapping[] modelViewMappings = new ModelViewMapping[1]; Map<Locale, String> descriptions = new HashMap<>(); List<XLinkConnectorView> views = new ArrayList<XLinkConnectorView>(); descriptions.put(Locale.ENGLISH, "This is a demo view."); descriptions.put(Locale.GERMAN, "Das ist eine demonstration view."); views = new ArrayList<XLinkConnectorView>(); views.add(new XLinkConnectorView(viewId1, toolName, descriptions)); views.add(new XLinkConnectorView(viewId2, toolName, descriptions)); modelViewMappings[0] = new ModelViewMapping( new ModelDescription( ExampleObjectOrientedModel.class.getName(), "3.0.0.SNAPSHOT") , views.toArray(new XLinkConnectorView[0])); return modelViewMappings; } @Test public void testGenerateXLink_shouldSucceed() { String connectorId = "testConnector"; String context = "foo"; String testClassName = "TestClass"; ExampleObjectOrientedModel modelObject = new ExampleObjectOrientedModel(); modelObject.setOoClassName(testClassName); String xlink = connectorManager.generateXLink(connectorId, context, modelObject); assertNotNull(xlink); // Trim baseURL away xlink = xlink.substring(xlink.indexOf("?") + 1); // Get parameters out of the link Map<String, String> parameterMap = new HashMap<>(); for (String parameter : xlink.split("&")) { String[] keyValue = parameter.split("="); if (keyValue.length == 2) { parameterMap.put(keyValue[0], keyValue[1]); } } assertEquals(context, parameterMap.get(XLinkConstants.XLINK_CONTEXTID_KEY)); assertNotNull(parameterMap.get(XLinkConstants.XLINK_IDENTIFIER_KEY)); String className = parameterMap.get(XLinkConstants.XLINK_MODELCLASS_KEY); assertEquals(ExampleObjectOrientedModel.class.getName(), className); ObjectMapper mapper = new ObjectMapper(); // Needed because OpenEngSB model tail can not be used here mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); String model = parameterMap.get(XLinkConstants.XLINK_IDENTIFIER_KEY); ExampleObjectOrientedModel exampleModel = null; try { exampleModel = mapper.readValue(urlDecodeParameter(model), ExampleObjectOrientedModel.class); } catch (IOException e) { fail("Should not have been thrown."); } assertNotNull(exampleModel); assertEquals(testClassName, exampleModel.getOoClassName()); } private String urlDecodeParameter(String parameter) { try { return URLDecoder.decode(parameter, "UTF-8"); } catch (UnsupportedEncodingException ex) { ex.printStackTrace(); } return parameter; } @Test public void testCreateConnectorWithSkipDomainType_shouldNotInvokeSetDomainType() throws Exception { Map<String, String> attributes = new HashMap<String, String>(); attributes.put(Constants.SKIP_SET_DOMAIN_TYPE, "true"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); NullDomainImpl mock2 = mock(NullDomainImpl.class); when(factory.createNewInstance(anyString())).thenReturn(mock2); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); connectorManager.create(connectorDescription); verify(mock2, never()).setDomainId(anyString()); verify(mock2, never()).setConnectorId(anyString()); } @Test public void testCreateConnectorWithToolModel_shouldCreateTransformingProxy() throws Exception { NullConnector mockedConnector = mock(NullConnector.class); when(factory.createNewInstance(anyString())).thenReturn(mockedConnector); Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); connectorManager.create(connectorDescription); NullDomain service = (NullDomain) serviceUtils.getService("(foo=bar)", 100L); service.nullMethod(); verify(mockedConnector).nullMethod(); } @Test public void testCallTransformingProxy_shouldTransformArguments() throws Exception { NullConnector mockedConnector = mock(NullConnector.class); when(factory.createNewInstance(anyString())).thenReturn(mockedConnector); Map<String, String> attributes = new HashMap<String, String>(); attributes.put("answer", "42"); Map<String, Object> properties = new Hashtable<String, Object>(); properties.put("foo", "bar"); ConnectorDescription connectorDescription = new ConnectorDescription("test", "testc", attributes, properties); connectorManager.create(connectorDescription); NullDomain service = (NullDomain) serviceUtils.getService("(foo=bar)", 100L); DummyModel dummyModel = new DummyModel(); dummyModel.setId("42"); dummyModel.setValue("foo"); NullModel nullModel = new NullModel(); nullModel.setId(42); nullModel.setValue("foo"); when(transformationEngine.performTransformation( any(ModelDescription.class), any(ModelDescription.class), eq(dummyModel))) .thenReturn(nullModel); service.commitModel(dummyModel); verify(mockedConnector).commitModel(nullModel); } @SuppressWarnings("unchecked") private void registerMockedFactory() throws Exception { factory = mock(ConnectorInstanceFactory.class); when(factory.createNewInstance(anyString())).thenReturn(new NullDomainImpl()); when(factory.applyAttributes(any(Connector.class), anyMap())).thenAnswer(new Answer<Connector>() { @Override public Connector answer(InvocationOnMock invocation) throws Throwable { return (Connector) invocation.getArguments()[0]; } }); Hashtable<String, Object> factoryProps = new Hashtable<String, Object>(); factoryProps.put(Constants.CONNECTOR_KEY, "testc"); factoryProps.put(Constants.DOMAIN_KEY, "test"); registerService(factory, factoryProps, ConnectorInstanceFactory.class); } private void registerMockedDomainProvider() { DomainProvider domainProvider = mock(DomainProvider.class); when(domainProvider.getDomainInterface()).thenAnswer(new Answer<Class<? extends Domain>>() { @Override public Class<? extends Domain> answer(InvocationOnMock invocation) throws Throwable { return NullDomain.class; } }); Hashtable<String, Object> domainProviderProps = new Hashtable<String, Object>(); domainProviderProps.put("domain", "test"); registerService(domainProvider, domainProviderProps, DomainProvider.class); } }