Java tutorial
/** * The MIT License * Copyright (c) 2015 the-james-burton * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jimsey.project.turbine.condenser.service; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.File; import java.lang.invoke.MethodHandles; import java.time.OffsetDateTime; import java.util.List; import org.apache.qpid.util.FileUtils; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; import org.jimsey.projects.turbine.condenser.component.InfrastructureProperties; import org.jimsey.projects.turbine.condenser.service.ElasticsearchNativeServiceImpl; import org.jimsey.projects.turbine.fuel.domain.DomainObjectGenerator; import org.jimsey.projects.turbine.fuel.domain.IndicatorJson; import org.jimsey.projects.turbine.fuel.domain.RandomDomainObjectGenerator; import org.jimsey.projects.turbine.fuel.domain.StrategyJson; import org.jimsey.projects.turbine.fuel.domain.TickJson; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MockServletContext.class) @ActiveProfiles("it") // @Ignore public class ElasticsearchNativeServiceImplTest { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final String elasticsearchTmpDir = "./target/elasticsearch"; private static final String market = "FTSE100"; private static final String stockOne = "ABC"; private static final String stockTwo = "DEF"; private static final String stockThree = "GHI"; @InjectMocks private ElasticsearchNativeServiceImpl service = new ElasticsearchNativeServiceImpl(); @Mock private InfrastructureProperties infrastructureProperties; private final DomainObjectGenerator rdogOne = new RandomDomainObjectGenerator(market, stockOne); private final DomainObjectGenerator rdogTwo = new RandomDomainObjectGenerator(market, stockTwo); private final ObjectMapper json = new ObjectMapper(); private static Client elasticsearch; private static Node node; private static final Integer elasticsearchNativePort = 9305; private static final Integer elasticsearchRestPort = 9205; private static final String elasticsearchCluster = "elasticsearch-test"; private static final String elasticsearchHost = "localhost"; private static final String indexForTicks = "turbine-ticks-test"; private static final String typeForTicks = "turbine-tick-test"; private static final String indexForIndicators = "turbine-indicators-test"; private static final String typeForIndicators = "turbine-indicators-test"; private static final String indexForStrategies = "turbine-strategies-test"; private static final String typeForStrategies = "turbine-strategies-test"; private boolean initialised = false; public ElasticsearchNativeServiceImplTest() { } @Before public void before() throws ElasticsearchException, JsonProcessingException, InterruptedException { if (!initialised) { MockitoAnnotations.initMocks(this); // TODO these are also present in the application-it.yml files, how to consolidate? when(infrastructureProperties.getElasticsearchCluster()).thenReturn(elasticsearchCluster); when(infrastructureProperties.getElasticsearchHost()).thenReturn(elasticsearchHost); when(infrastructureProperties.getElasticsearchNativePort()).thenReturn(elasticsearchNativePort); when(infrastructureProperties.getElasticsearchRestPort()).thenReturn(elasticsearchRestPort); when(infrastructureProperties.getElasticsearchIndexForTicks()).thenReturn(indexForTicks); when(infrastructureProperties.getElasticsearchTypeForTicks()).thenReturn(typeForTicks); when(infrastructureProperties.getElasticsearchIndexForIndicators()).thenReturn(indexForIndicators); when(infrastructureProperties.getElasticsearchTypeForIndicators()).thenReturn(typeForIndicators); when(infrastructureProperties.getElasticsearchIndexForStrategies()).thenReturn(indexForStrategies); when(infrastructureProperties.getElasticsearchTypeForStrategies()).thenReturn(typeForStrategies); // TODO should I expect this @PostConstruct be called automatically for me? service.init(); initialised = true; } } @BeforeClass public static void beforeClass() throws Exception { logger.info("setup()"); FileUtils.delete(new File(elasticsearchTmpDir), true); Settings settings = Settings.builder().put("path.home", elasticsearchTmpDir) .put("path.conf", elasticsearchTmpDir).put("path.data", elasticsearchTmpDir) .put("path.work", elasticsearchTmpDir).put("path.logs", elasticsearchTmpDir) .put("http.port", elasticsearchRestPort).put("transport.tcp.port", elasticsearchNativePort) .put("index.number_of_shards", "1").put("index.number_of_replicas", "0") .put("discovery.zen.ping.multicast.enabled", "false").build(); node = NodeBuilder.nodeBuilder().data(true).client(false).settings(settings) .clusterName(elasticsearchCluster).node(); node.start(); elasticsearch = node.client(); } @After public void tearDown() { logger.debug("tearDown()"); // delete whole index, not each id... deleteElasticsearch(indexForTicks); deleteElasticsearch(indexForIndicators); deleteElasticsearch(indexForStrategies); refreshElasticsearch(); } @Test public void testGetAllTicks() throws Exception { int number = 12; logger.info("given any {} ticks", number); populateElasticsearch(rdogOne, number, indexForTicks, typeForTicks, null, TickJson.class); logger.info("it should return all ticks"); String result = service.getAllTicks(); logger.info(" *** getAllTicks(): {}", result); @SuppressWarnings("unchecked") List<TickJson> ticks = (List<TickJson>) json.readValue(result, List.class); logger.info("expected:{}, actual:{}", number, ticks.size()); assertThat(ticks, hasSize(number)); assertThat(result, containsString("timestamp")); } @Test public void testFindTicksByMarketAndSymbol() throws Exception { int numberOne = 5; int numberTwo = 7; logger.info("given {} {} ticks and {} {} ticks", numberOne, stockOne, numberTwo, stockTwo); populateElasticsearch(rdogOne, numberOne, indexForTicks, typeForTicks, null, TickJson.class); populateElasticsearch(rdogTwo, numberTwo, indexForTicks, typeForTicks, null, TickJson.class); logger.info("it should return {} {} ticks, {} {} ticks and 0 {} ticks", numberOne, stockOne, numberTwo, stockTwo, stockThree); List<TickJson> resultOne = service.findTicksByMarketAndSymbol(market, stockOne); List<TickJson> resultTwo = service.findTicksByMarketAndSymbol(market, stockTwo); List<TickJson> resultThree = service.findTicksByMarketAndSymbol(market, stockThree); logger.info(" *** findTicksByMarketAndSymbol({}): {}, expected: {}", stockOne, resultOne.size(), numberOne); logger.info(" *** findTicksByMarketAndSymbol({}): {}, expected: {}", stockTwo, resultTwo.size(), numberTwo); logger.info(" *** findTicksByMarketAndSymbol({}): {}, expected: {}", stockThree, resultThree.size(), 0); assertThat(resultOne, hasSize(numberOne)); assertThat(resultTwo, hasSize(numberTwo)); assertThat(resultThree, hasSize(0)); } @Test public void testFindIndicatorsByMarketAndSymbol() throws Exception { int numberOne = 5; int numberTwo = 7; logger.info("given {} {} indicators and {} {} indicators", numberOne, stockOne, numberTwo, stockTwo); String name = "indicator-name"; populateElasticsearch(rdogOne, numberOne, indexForIndicators, typeForIndicators, name, IndicatorJson.class); populateElasticsearch(rdogTwo, numberTwo, indexForIndicators, typeForIndicators, name, IndicatorJson.class); logger.info("it should return {} {} indictors, {} {} indicators and 0 {} indicators", numberOne, stockOne, numberTwo, stockTwo, stockThree); List<IndicatorJson> resultOne = service.findIndicatorsByMarketAndSymbol(market, stockOne); List<IndicatorJson> resultTwo = service.findIndicatorsByMarketAndSymbol(market, stockTwo); List<IndicatorJson> resultThree = service.findIndicatorsByMarketAndSymbol(market, stockThree); logger.info(" *** findIndicatorsByMarketAndSymbol({}): {}, expected: {}", stockOne, resultOne.size(), numberOne); logger.info(" *** findIndicatorsByMarketAndSymbol({}): {}, expected: {}", stockTwo, resultTwo.size(), numberTwo); logger.info(" *** findIndicatorsByMarketAndSymbol({}): {}, expected: {}", stockThree, resultThree.size(), 0); assertThat(resultOne, hasSize(numberOne)); assertThat(resultTwo, hasSize(numberTwo)); assertThat(resultThree, hasSize(0)); } @Test public void testFindTicksByMarketAndSymbolAndDateGreaterThan() throws Exception { int number = 7; int expected = 4; logger.info("given {} {} ticks", number, stockOne); long midpoint = populateElasticsearch(rdogOne, number, indexForTicks, typeForTicks, null, TickJson.class) .getDate(); logger.info("it should return only {} {} ticks after the midpoint", expected, stockOne); List<TickJson> result = service.findTicksByMarketAndSymbolAndDateGreaterThan(market, stockOne, midpoint); logger.info(" *** findTicksByMarketAndSymbolAndDateGreaterThan({}, {}): {}, expected: {}", stockOne, midpoint, result.size(), expected); assertThat(result, hasSize(expected)); } @Test public void testFindIndicatorsByMarketAndSymbolAndDateGreaterThan() throws Exception { int number = 7; int expected = 4; logger.info("given {} {} indicators", number, stockOne); long midpoint = populateElasticsearch(rdogOne, number, indexForIndicators, typeForIndicators, null, IndicatorJson.class).getDate(); logger.info("it should return only {} {} indicators after the midpoint", expected, stockOne); List<IndicatorJson> result = service.findIndicatorsByMarketAndSymbolAndDateGreaterThan(market, stockOne, midpoint); logger.info(" *** findIndicatorsByMarketAndSymbolAndDateGreaterThan({}, {}): {}, expected: {}", stockOne, midpoint, result.size(), expected); assertThat(result, hasSize(expected)); } @Test public void testFindIndicatorsByMarketAndSymbolAndNameAndDateGreaterThan() throws Exception { int number = 7; int expected = 7; final String nameOne = "indicator-one"; final String nameTwo = "indicator-two"; logger.info("given {} {} {} indicators and {} {} {} indicators", number, stockOne, nameOne, number, stockOne, nameTwo); populateElasticsearch(rdogOne, number, indexForIndicators, typeForIndicators, nameOne, IndicatorJson.class); long midpoint = OffsetDateTime.now().toInstant().toEpochMilli(); populateElasticsearch(rdogOne, number, indexForIndicators, typeForIndicators, nameTwo, IndicatorJson.class); logger.info("it should return only {} {} {} indicators after the midpoint", expected, nameOne, stockOne); List<IndicatorJson> result = service.findIndicatorsByMarketAndSymbolAndNameAndDateGreaterThan(market, stockOne, nameOne, midpoint); logger.info(" *** findIndicatorsByMarketAndSymbolAndNameAndDateGreaterThan({}, {}, {}): {}, expected: {}", stockOne, nameOne, midpoint, result.size(), expected); assertThat(result, hasSize(expected)); } // ------------------------------------------------------ /** * Populate elasticsearch with the given number of the given objects * * @param rdog the RandomDomainObjectGenerator to use * @param number how many objects to create * @param index the index to use in elasticsearch * @param type the type to use in elasticsearch * @param name the name of the indicator or strategy (ignored for ticks) * @param t what type of objects to create * @return the object in the middle of the populate, by timestamp * @throws Exception */ @SuppressWarnings("unchecked") private <T> T populateElasticsearch(DomainObjectGenerator rdog, int number, String index, String type, String name, Class<T> t) throws Exception { int x = 0; int midpoint = (number / 2) + 1; Object result = null; Object current = null; while (++x <= number) { // cannot use switch statement easily here due to generics... if (TickJson.class.getName().equals(t.getName())) { current = rdog.newTick(); } if (IndicatorJson.class.getName().equals(t.getName())) { current = rdog.newIndicator(name); } if (StrategyJson.class.getName().equals(t.getName())) { current = rdog.newStrategy(name); } if (current == null) { throw new Exception(String.format("unsupported class: %s", t.getName())); } index(index, type, current); if (x == midpoint) { logger.debug("midpoint: {}", x); result = current; } } refreshElasticsearch(); return (T) result; } /** * This will cause elasticsearch to 'flush' ensuring that queries return correctly. * Should be called after every data population. */ private void refreshElasticsearch() { refreshElasticsearch(indexForTicks); refreshElasticsearch(indexForIndicators); refreshElasticsearch(indexForStrategies); } private void refreshElasticsearch(String index) { try { RefreshResponse refreshResponse = elasticsearch.admin().indices().prepareRefresh(index).get(); logger.debug("failed: {}", refreshResponse.getFailedShards()); logger.debug("succeeded: {}", refreshResponse.getSuccessfulShards()); } catch (Exception e) { logger.debug(e.getMessage()); } } /** * Index a given object into elasticsearch. The object must be serializable with Jackson. * * @param index the elasticserch index to write the object into * @param type the type of the object being written * @param object the object to write into the index * @return the id of the object indexed by elasticsearch * @throws ElasticsearchException * @throws JsonProcessingException */ private String index(String index, String type, Object object) throws ElasticsearchException, JsonProcessingException { IndexResponse response = elasticsearch.prepareIndex(index, type).setSource(json.writeValueAsBytes(object)) .get(); logger.debug("successfully indexed new object: index:{}, type:{}, id:{}, object:{}", response.getIndex(), response.getType(), response.getId(), object); return response.getId(); } /** * removes all known indexes from the in-process elasticsearch * * @param index the elasticsearch index to delete from * @param type the type of the object being deleted * @param id the id of the object being deleted */ private void deleteElasticsearch(String index) { DeleteIndexResponse response = null; try { response = elasticsearch.admin().indices().prepareDelete(index).get(); logger.debug("successfully deleted: index:{}, headers:{}", index, response.getHeaders()); } catch (Exception e) { logger.debug(e.getMessage()); } } }