diff options
author | wasala <przemyslaw.wasala@nokia.com> | 2018-09-19 12:55:18 +0200 |
---|---|---|
committer | wasala <przemyslaw.wasala@nokia.com> | 2018-09-20 12:50:32 +0200 |
commit | 3468d474187ef01546bdf1180d11453a4f924d31 (patch) | |
tree | 79aafcd9b4f7d36d27d0412f0ac3946c0ad79034 /datafile-app-server/src/test/java | |
parent | 2d1ea513ca0386102f2f9c11bd5c76f37939113a (diff) |
Loading configuration from consul/cbs
*Registered task which calling cbs/consul
for configuration - fixedRate 5 minutes
*Added workflow for loading config from cloud
Change-Id: Iba36d18b4ee0dca082612fa4c92c877f71c9b1fe
Issue-ID: DCAEGEN2-784
Signed-off-by: wasala <przemyslaw.wasala@nokia.com>
Diffstat (limited to 'datafile-app-server/src/test/java')
6 files changed, 286 insertions, 15 deletions
diff --git a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/configuration/CloudConfigParserTest.java b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/configuration/CloudConfigParserTest.java new file mode 100644 index 00000000..a4f098be --- /dev/null +++ b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/configuration/CloudConfigParserTest.java @@ -0,0 +1,104 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * 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.dcaegen2.collectors.datafile.configuration; + + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import org.junit.jupiter.api.Test; +import org.onap.dcaegen2.collectors.datafile.config.DmaapConsumerConfiguration; +import org.onap.dcaegen2.collectors.datafile.config.DmaapPublisherConfiguration; +import org.onap.dcaegen2.collectors.datafile.config.ImmutableDmaapConsumerConfiguration; +import org.onap.dcaegen2.collectors.datafile.config.ImmutableDmaapPublisherConfiguration; + +class CloudConfigParserTest { + + private static final String correctJson = + "{\"dmaap.dmaapProducerConfiguration.dmaapTopicName\": \"/events/unauthenticated.VES_NOTIFICATION_OUTPUT\", " + + "\"dmaap.dmaapConsumerConfiguration.timeoutMS\": -1," + + " \"dmaap.dmaapConsumerConfiguration.dmaapHostName\": \"message-router.onap.svc.cluster.local\"," + + "\"dmaap.dmaapConsumerConfiguration.dmaapUserName\": \"admin\", " + + "\"dmaap.dmaapProducerConfiguration.dmaapPortNumber\": 3904, " + + "\"dmaap.dmaapConsumerConfiguration.dmaapUserPassword\": \"admin\", " + + "\"dmaap.dmaapProducerConfiguration.dmaapProtocol\": \"http\", " + + "\"dmaap.dmaapProducerConfiguration.dmaapContentType\": \"application/json\", " + + "\"dmaap.dmaapConsumerConfiguration.dmaapTopicName\": \"/events/unauthenticated.VES_NOTIFICATION_OUTPUT\", " + + "\"dmaap.dmaapConsumerConfiguration.dmaapPortNumber\": 3904, " + + "\"dmaap.dmaapConsumerConfiguration.dmaapContentType\": \"application/json\", " + + "\"dmaap.dmaapConsumerConfiguration.messageLimit\": -1, " + + "\"dmaap.dmaapConsumerConfiguration.dmaapProtocol\": \"http\", " + + "\"dmaap.dmaapConsumerConfiguration.consumerId\": \"c12\"," + + "\"dmaap.dmaapProducerConfiguration.dmaapHostName\": \"message-router.onap.svc.cluster.local\", " + + "\"dmaap.dmaapConsumerConfiguration.consumerGroup\": \"OpenDCAE-c12\", " + + "\"dmaap.dmaapProducerConfiguration.dmaapUserName\": \"admin\", " + + "\"dmaap.dmaapProducerConfiguration.dmaapUserPassword\": \"admin\"}"; + + private static final ImmutableDmaapConsumerConfiguration correctDmaapConsumerConfig = + new ImmutableDmaapConsumerConfiguration.Builder() + .timeoutMS(-1) + .dmaapHostName("message-router.onap.svc.cluster.local") + .dmaapUserName("admin") + .dmaapUserPassword("admin") + .dmaapTopicName("/events/unauthenticated.VES_NOTIFICATION_OUTPUT") + .dmaapPortNumber(3904) + .dmaapContentType("application/json") + .messageLimit(-1) + .dmaapProtocol("http") + .consumerId("c12") + .consumerGroup("OpenDCAE-c12") + .build(); + + private static final ImmutableDmaapPublisherConfiguration correctDmaapPublisherConfig = + new ImmutableDmaapPublisherConfiguration.Builder() + .dmaapTopicName("/events/unauthenticated.VES_NOTIFICATION_OUTPUT") + .dmaapUserPassword("admin") + .dmaapPortNumber(3904) + .dmaapProtocol("http") + .dmaapContentType("application/json") + .dmaapHostName("message-router.onap.svc.cluster.local") + .dmaapUserName("admin") + .build(); + + private CloudConfigParser cloudConfigParser = new CloudConfigParser( + new Gson().fromJson(correctJson, JsonObject.class)); + + + @Test + public void shouldCreateDmaapConsumerConfigurationCorrectly() { + // when + DmaapConsumerConfiguration dmaapConsumerConfig = cloudConfigParser.getDmaapConsumerConfig(); + + // then + assertThat(dmaapConsumerConfig).isNotNull(); + assertThat(dmaapConsumerConfig).isEqualToComparingFieldByField(correctDmaapConsumerConfig); + } + + + @Test + public void shouldCreateDmaapPublisherConfigurationCorrectly() { + // when + DmaapPublisherConfiguration dmaapPublisherConfig = cloudConfigParser.getDmaapPublisherConfig(); + + // then + assertThat(dmaapPublisherConfig).isNotNull(); + assertThat(dmaapPublisherConfig).isEqualToComparingFieldByField(correctDmaapPublisherConfig); + } +}
\ No newline at end of file diff --git a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/ftp/FileCollectorTest.java b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/ftp/FileCollectorTest.java index 5b9d0aaf..2f61ac97 100644 --- a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/ftp/FileCollectorTest.java +++ b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/ftp/FileCollectorTest.java @@ -28,14 +28,13 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.onap.dcaegen2.collectors.datafile.model.ConsumerDmaapModel; -import org.onap.dcaegen2.collectors.datafile.service.FileData; -import org.onap.dcaegen2.collectors.datafile.service.ImmutableFileData; +import org.onap.dcaegen2.collectors.datafile.model.FileData; +import org.onap.dcaegen2.collectors.datafile.model.ImmutableFileData; import reactor.core.publisher.Mono; /** * @author <a href="mailto:henrik.b.andersson@est.tech">Henrik Andersson</a> - * */ public class FileCollectorTest { @@ -64,15 +63,15 @@ public class FileCollectorTest { public void whenSingleFtpesFile_returnCorrectResponse() { List<FileData> listOfFileData = new ArrayList<FileData>(); listOfFileData.add(ImmutableFileData.builder().changeIdentifier(PM_MEAS_CHANGE_IDINTIFIER) - .changeType(FILE_READY_CHANGE_TYPE).location(FTPES_LOCATION).compression(GZIP_COMPRESSION) - .fileFormatType(MEAS_COLLECT_FILE_FORMAT_TYPE).fileFormatVersion(FILE_FORMAT_VERSION).build()); + .changeType(FILE_READY_CHANGE_TYPE).location(FTPES_LOCATION).compression(GZIP_COMPRESSION) + .fileFormatType(MEAS_COLLECT_FILE_FORMAT_TYPE).fileFormatVersion(FILE_FORMAT_VERSION).build()); FileServerData fileServerData = ImmutableFileServerData.builder().serverAddress(SERVER_ADDRESS).port(PORT_22) - .userId("").password("").build(); + .userId("").password("").build(); when(ftpsClientMock.collectFile(fileServerData, REMOTE_FILE_LOCATION, LOCAL_FILE_LOCATION)).thenReturn(true); Mono<List<ConsumerDmaapModel>> consumerModelsMono = - fileCollectorUndetTest.getFilesFromSender(listOfFileData); + fileCollectorUndetTest.getFilesFromSender(listOfFileData); List<ConsumerDmaapModel> consumerModels = consumerModelsMono.block(); assertEquals(1, consumerModels.size()); @@ -82,7 +81,7 @@ public class FileCollectorTest { assertEquals(FILE_FORMAT_VERSION, consumerDmaapModel.getFileFormatVersion()); assertEquals(LOCAL_FILE_LOCATION, consumerDmaapModel.getLocation()); FileServerData expectedFileServerData = ImmutableFileServerData.builder().serverAddress(SERVER_ADDRESS) - .userId("").password("").port(PORT_22).build(); + .userId("").password("").port(PORT_22).build(); verify(ftpsClientMock, times(1)).collectFile(expectedFileServerData, REMOTE_FILE_LOCATION, LOCAL_FILE_LOCATION); verifyNoMoreInteractions(ftpsClientMock); } @@ -91,15 +90,15 @@ public class FileCollectorTest { public void whenSingleSftpFile_returnCorrectResponse() { List<FileData> listOfFileData = new ArrayList<FileData>(); listOfFileData.add(ImmutableFileData.builder().changeIdentifier(PM_MEAS_CHANGE_IDINTIFIER) - .changeType(FILE_READY_CHANGE_TYPE).location(SFTP_LOCATION).compression(GZIP_COMPRESSION) - .fileFormatType(MEAS_COLLECT_FILE_FORMAT_TYPE).fileFormatVersion(FILE_FORMAT_VERSION).build()); + .changeType(FILE_READY_CHANGE_TYPE).location(SFTP_LOCATION).compression(GZIP_COMPRESSION) + .fileFormatType(MEAS_COLLECT_FILE_FORMAT_TYPE).fileFormatVersion(FILE_FORMAT_VERSION).build()); FileServerData fileServerData = ImmutableFileServerData.builder().serverAddress(SERVER_ADDRESS).port(PORT_22) - .userId("").password("").build(); + .userId("").password("").build(); when(sftpClientMock.collectFile(fileServerData, REMOTE_FILE_LOCATION, LOCAL_FILE_LOCATION)).thenReturn(true); Mono<List<ConsumerDmaapModel>> consumerModelsMono = - fileCollectorUndetTest.getFilesFromSender(listOfFileData); + fileCollectorUndetTest.getFilesFromSender(listOfFileData); List<ConsumerDmaapModel> consumerModels = consumerModelsMono.block(); assertEquals(1, consumerModels.size()); @@ -109,7 +108,7 @@ public class FileCollectorTest { assertEquals(FILE_FORMAT_VERSION, consumerDmaapModel.getFileFormatVersion()); assertEquals(LOCAL_FILE_LOCATION, consumerDmaapModel.getLocation()); FileServerData expectedFileServerData = ImmutableFileServerData.builder().serverAddress(SERVER_ADDRESS) - .userId("").password("").port(PORT_22).build(); + .userId("").password("").port(PORT_22).build(); verify(sftpClientMock, times(1)).collectFile(expectedFileServerData, REMOTE_FILE_LOCATION, LOCAL_FILE_LOCATION); verifyNoMoreInteractions(ftpsClientMock); } diff --git a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DatafileConfigurationProviderTest.java b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DatafileConfigurationProviderTest.java new file mode 100644 index 00000000..efd89837 --- /dev/null +++ b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DatafileConfigurationProviderTest.java @@ -0,0 +1,90 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * 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.dcaegen2.collectors.datafile.service; + + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.junit.jupiter.api.Test; +import org.onap.dcaegen2.collectors.datafile.model.EnvProperties; +import org.onap.dcaegen2.collectors.datafile.model.ImmutableEnvProperties; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +class DatafileConfigurationProviderTest { + private static final Gson gson = new Gson(); + private static final String configBindingService = "[{\"ID\":\"9c8dd674-34ce-7049-d318-e98d93a64303\",\"Node\"" + + ":\"dcae-bootstrap\",\"Address\":\"10.42.52.82\",\"Datacenter\":\"dc1\",\"TaggedAddresses\":" + + "{\"lan\":\"10.42.52.82\",\"wan\":\"10.42.52.82\"},\"NodeMeta\":{\"consul-network-segment\":\"\"}," + + "\"ServiceID\":\"dcae-cbs1\",\"ServiceName\":\"config-binding-service\",\"ServiceTags\":[]," + + "\"ServiceAddress\":\"config-binding-service\",\"ServicePort\":10000,\"ServiceEnableTagOverride\":false," + + "\"CreateIndex\":14352,\"ModifyIndex\":14352},{\"ID\":\"35c6f540-a29c-1a92-23b0-1305bd8c81f5\",\"Node\":" + + "\"dev-consul-server-1\",\"Address\":\"10.42.165.51\",\"Datacenter\":\"dc1\",\"TaggedAddresses\":" + + "{\"lan\":\"10.42.165.51\",\"wan\":\"10.42.165.51\"},\"NodeMeta\":{\"consul-network-segment\":\"\"}," + + "\"ServiceID\":\"dcae-cbs1\",\"ServiceName\":\"config-binding-service\",\"ServiceTags\":[]," + + "\"ServiceAddress\":\"config-binding-service\",\"ServicePort\":10000,\"ServiceEnableTagOverride\":false," + + "\"CreateIndex\":803,\"ModifyIndex\":803}]"; + private static final JsonArray configBindingServiceJson = gson.fromJson(configBindingService, JsonArray.class); + private static final JsonArray emptyConfigBindingServiceJson = gson.fromJson("[]", JsonArray.class); + private static final String datafileCollectorMockConfiguration = "{\"test\":1}"; + private static final JsonObject datafileCollectorMockConfigurationJson = gson.fromJson(datafileCollectorMockConfiguration, JsonObject.class); + + private EnvProperties envProperties = ImmutableEnvProperties.builder() + .appName("dcae-datafile-collector") + .cbsName("config-binding-service") + .consulHost("consul") + .consulPort(8500) + .build(); + + @Test + void shouldReturnDatafileCollectorConfiguration() { + // given + HttpGetClient webClient = mock(HttpGetClient.class); + when( + webClient.callHttpGet("http://consul:8500/v1/catalog/service/config-binding-service", JsonArray.class)) + .thenReturn(Mono.just(configBindingServiceJson)); + when(webClient.callHttpGet("http://config-binding-service:10000/service_component/dcae-datafile-collector", JsonObject.class)) + .thenReturn(Mono.just(datafileCollectorMockConfigurationJson)); + + DatafileConfigurationProvider provider = new DatafileConfigurationProvider(webClient); + + //when/then + StepVerifier.create(provider.callForDataFileCollectorConfiguration(envProperties)).expectSubscription() + .expectNext(datafileCollectorMockConfigurationJson).verifyComplete(); + } + + @Test + void shouldReturnMonoErrorWhenConsuleDoesntHaveConfigBindingServiceEntry() { + // given + HttpGetClient webClient = mock(HttpGetClient.class); + when( + webClient.callHttpGet("http://consul:8500/v1/catalog/service/config-binding-service", JsonArray.class)) + .thenReturn(Mono.just(emptyConfigBindingServiceJson)); + + DatafileConfigurationProvider provider = new DatafileConfigurationProvider(webClient); + + //when/then + StepVerifier.create(provider.callForDataFileCollectorConfiguration(envProperties)).expectSubscription() + .expectError(IllegalStateException.class).verify(); + } +}
\ No newline at end of file diff --git a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DmaapConsumerJsonParserTest.java b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DmaapConsumerJsonParserTest.java index 4aad5f45..8c36a51f 100644 --- a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DmaapConsumerJsonParserTest.java +++ b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/DmaapConsumerJsonParserTest.java @@ -26,6 +26,8 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.onap.dcaegen2.collectors.datafile.exceptions.DmaapNotFoundException; +import org.onap.dcaegen2.collectors.datafile.model.FileData; +import org.onap.dcaegen2.collectors.datafile.model.ImmutableFileData; import org.onap.dcaegen2.collectors.datafile.utils.JsonMessage; import org.onap.dcaegen2.collectors.datafile.utils.JsonMessage.AdditionalField; @@ -44,6 +46,7 @@ class DmaapConsumerJsonParserTest { @Test void whenPassingCorrectJson_validationNotThrowingAnException() throws DmaapNotFoundException { AdditionalField additionalField = new JsonMessage.AdditionalFieldBuilder().name("A20161224.1030-1045.bin.gz") + .location("ftpes://192.168.0.101:22/ftp/rop/A20161224.1030-1045.bin.gz").compression("gzip") .fileFormatType("org.3GPP.32.435#measCollec").fileFormatVersion("V10").build(); JsonMessage message = new JsonMessage.JsonMessageBuilder().changeIdentifier("PM_MEAS_FILES") diff --git a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/HttpGetClientTest.java b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/HttpGetClientTest.java new file mode 100644 index 00000000..d95e3417 --- /dev/null +++ b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/service/HttpGetClientTest.java @@ -0,0 +1,75 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2018 NOKIA Intellectual Property. All rights reserved. + * ================================================================================ + * 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.dcaegen2.collectors.datafile.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import org.junit.jupiter.api.Test; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +class HttpGetClientTest { + + private static final String SOMEURL = "http://someurl"; + private static final String DATA = "{}"; + private Gson gson = new Gson(); + private WebClient webClient = mock(WebClient.class); + private WebClient.RequestHeadersUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); + private WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class); + + @Test + void shouldReturnJsonObjectOnGetCall() { + //given + mockWebClientDependantObject(); + HttpGetClient httpGetClient = new HttpGetClient(webClient); + when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just(DATA)); + + //when/then + StepVerifier.create(httpGetClient.callHttpGet(SOMEURL, JsonObject.class)).expectSubscription() + .expectNext(gson.fromJson(DATA, JsonObject.class)).verifyComplete(); + } + + @Test + void shouldReturnMonoErrorOnGetCall() { + //given + mockWebClientDependantObject(); + HttpGetClient httpGetClient = new HttpGetClient(webClient); + when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just("some wrong data")); + + //when/then + StepVerifier.create(httpGetClient.callHttpGet(SOMEURL, JsonObject.class)).expectSubscription() + .expectError(JsonSyntaxException.class).verify(); + } + + + private void mockWebClientDependantObject() { + doReturn(requestBodyUriSpec).when(webClient).get(); + when(requestBodyUriSpec.uri(SOMEURL)).thenReturn(requestBodyUriSpec); + doReturn(responseSpec).when(requestBodyUriSpec).retrieve(); + doReturn(responseSpec).when(responseSpec).onStatus(any(), any()); + doReturn(responseSpec).when(responseSpec).onStatus(any(), any()); + } +}
\ No newline at end of file diff --git a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/tasks/DmaapConsumerTaskImplTest.java b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/tasks/DmaapConsumerTaskImplTest.java index c21c5988..e6818453 100644 --- a/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/tasks/DmaapConsumerTaskImplTest.java +++ b/datafile-app-server/src/test/java/org/onap/dcaegen2/collectors/datafile/tasks/DmaapConsumerTaskImplTest.java @@ -38,9 +38,9 @@ import org.onap.dcaegen2.collectors.datafile.exceptions.DmaapEmptyResponseExcept import org.onap.dcaegen2.collectors.datafile.ftp.FileCollector; import org.onap.dcaegen2.collectors.datafile.model.ConsumerDmaapModel; import org.onap.dcaegen2.collectors.datafile.model.ImmutableConsumerDmaapModel; +import org.onap.dcaegen2.collectors.datafile.model.ImmutableFileData; import org.onap.dcaegen2.collectors.datafile.service.DmaapConsumerJsonParser; -import org.onap.dcaegen2.collectors.datafile.service.FileData; -import org.onap.dcaegen2.collectors.datafile.service.ImmutableFileData; +import org.onap.dcaegen2.collectors.datafile.model.FileData; import org.onap.dcaegen2.collectors.datafile.service.consumer.DmaapConsumerReactiveHttpClient; import org.onap.dcaegen2.collectors.datafile.utils.JsonMessage; import org.onap.dcaegen2.collectors.datafile.utils.JsonMessage.AdditionalField; |