diff --git a/src/main/java/ch/psi/daq/queryrest/QueryRestApplication.java b/src/main/java/ch/psi/daq/queryrest/QueryRestApplication.java index 102ef95..c72e1b1 100644 --- a/src/main/java/ch/psi/daq/queryrest/QueryRestApplication.java +++ b/src/main/java/ch/psi/daq/queryrest/QueryRestApplication.java @@ -1,32 +1,26 @@ package ch.psi.daq.queryrest; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.support.SpringBootServletInitializer; /** * Entry point to our rest-frontend of the data acquisition (DAQ) application which most importantly * wires all the @RestController annotated classes. */ -@SpringBootApplication -@EnableAutoConfiguration(exclude={ - CassandraDataAutoConfiguration.class -}) -//@EnableEncryptableProperties +@SpringBootApplication( + exclude = { + CassandraDataAutoConfiguration.class + }, + scanBasePackageClasses = { + QueryRestApplication.class + }) +//@EnableEncryptableProperties // http://stackoverflow.com/questions/26655875/spring-boot-redirect-http-to-https -public class QueryRestApplication extends SpringBootServletInitializer { +public class QueryRestApplication { public static void main(final String[] args) { System.getProperties().setProperty("hazelcast.phone.home.enabled", "false"); SpringApplication.run(QueryRestApplication.class, args); } - - @Override - protected final SpringApplicationBuilder configure(final SpringApplicationBuilder application) { - return application.sources(QueryRestApplication.class); - } - } diff --git a/src/main/java/ch/psi/daq/queryrest/config/QueryRestConfig.java b/src/main/java/ch/psi/daq/queryrest/config/QueryRestConfig.java index a4e16d9..d3a911e 100644 --- a/src/main/java/ch/psi/daq/queryrest/config/QueryRestConfig.java +++ b/src/main/java/ch/psi/daq/queryrest/config/QueryRestConfig.java @@ -39,6 +39,7 @@ import ch.psi.daq.domain.query.operation.Aggregation; import ch.psi.daq.domain.query.operation.QueryField; import ch.psi.daq.domain.query.operation.aggregation.extrema.AbstractExtremaMeta; import ch.psi.daq.domain.query.response.Response; +import ch.psi.daq.domain.request.validate.RequestProviderValidator; import ch.psi.daq.query.analyzer.BackendQueryAnalyzerImpl; import ch.psi.daq.query.config.QueryConfig; import ch.psi.daq.queryrest.controller.validator.QueryValidator; @@ -85,6 +86,7 @@ public class QueryRestConfig { // extends WebMvcConfigurerAdapter { public static final String BEAN_NAME_QUERY_MANAGER = "queryManager"; public static final String BEAN_NAME_QUERY_ANALIZER_FACTORY = "queryAnalizerFactory"; public static final String BEAN_NAME_QUERY_VALIDATOR = "queryValidator"; + public static final String BEAN_NAME_REQUEST_PROVIDER_VALIDATOR = "requestProviderValidator"; public static final String BEAN_NAME_JSON_FACTORY = "jsonFactory"; public static final String BEAN_NAME_MSG_PACK_FACTORY = "msgPackFactory"; public static final String BEAN_NAME_SMILE_FACTORY = "smileFactory"; @@ -126,28 +128,30 @@ public class QueryRestConfig { // extends WebMvcConfigurerAdapter { objectMapper.addMixIn(Response.class, PolymorphicResponseMixIn.class); } -// @Override -// public void configureMessageConverters(List> converters) { -// final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); -// /** -// * This is necessary so that the message conversion uses the configured object mapper. -// * Otherwise, a separate object mapper is instantiated for Springs message conversion. -// */ -// converter.setObjectMapper(objectMapper); -// converters.add(converter); -// super.configureMessageConverters(converters); -// } - -// does this work for json comming from web-requests -// -> use WebMvcConfigurationSupport? -// https://stackoverflow.com/questions/26639475/how-to-set-context-param-in-spring-boot -// @Bean -// @Lazy -// public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { -// final MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); -// jsonConverter.setObjectMapper(objectMapper); -// return jsonConverter; -// } + // @Override + // public void configureMessageConverters(List> converters) { + // final MappingJackson2HttpMessageConverter converter = new + // MappingJackson2HttpMessageConverter(); + // /** + // * This is necessary so that the message conversion uses the configured object mapper. + // * Otherwise, a separate object mapper is instantiated for Springs message conversion. + // */ + // converter.setObjectMapper(objectMapper); + // converters.add(converter); + // super.configureMessageConverters(converters); + // } + + // does this work for json comming from web-requests + // -> use WebMvcConfigurationSupport? + // https://stackoverflow.com/questions/26639475/how-to-set-context-param-in-spring-boot + // @Bean + // @Lazy + // public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { + // final MappingJackson2HttpMessageConverter jsonConverter = new + // MappingJackson2HttpMessageConverter(); + // jsonConverter.setObjectMapper(objectMapper); + // return jsonConverter; + // } @Bean(name = BEAN_NAME_JSON_FACTORY) @Lazy @@ -267,4 +271,10 @@ public class QueryRestConfig { // extends WebMvcConfigurerAdapter { public Validator queryValidator() { return new QueryValidator(); } + + @Bean(name = BEAN_NAME_REQUEST_PROVIDER_VALIDATOR) + @Lazy + public Validator requestProviderValidator() { + return new RequestProviderValidator(); + } } diff --git a/src/main/java/ch/psi/daq/queryrest/controller/QueryRestController.java b/src/main/java/ch/psi/daq/queryrest/controller/QueryRestController.java index 6ab287d..da04b2f 100644 --- a/src/main/java/ch/psi/daq/queryrest/controller/QueryRestController.java +++ b/src/main/java/ch/psi/daq/queryrest/controller/QueryRestController.java @@ -1,5 +1,7 @@ package ch.psi.daq.queryrest.controller; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -15,6 +17,9 @@ import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.http.MediaType; +import org.springframework.validation.DirectFieldBindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.ObjectError; import org.springframework.validation.Validator; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; @@ -46,7 +51,6 @@ import ch.psi.daq.domain.query.response.Response; import ch.psi.daq.domain.query.response.ResponseFormat; import ch.psi.daq.domain.query.transform.image.color.ColorModelType; import ch.psi.daq.domain.query.transform.image.resize.ValueAggregation; -import ch.psi.daq.domain.request.validate.RequestProviderValidator; import ch.psi.daq.queryrest.config.QueryRestConfig; import ch.psi.daq.queryrest.query.QueryManager; import ch.psi.daq.queryrest.response.AbstractHTTPResponse; @@ -63,7 +67,7 @@ public class QueryRestController implements ApplicationContextAware { private ObjectMapper objectMapper; private QueryManager queryManager; private Validator queryValidator; - private Validator requestProviderValidator = new RequestProviderValidator(); + private Validator requestProviderValidator; private Response defaultResponse = new JSONHTTPResponse(); @SuppressWarnings("unchecked") @@ -77,6 +81,7 @@ public class QueryRestController implements ApplicationContextAware { objectMapper = context.getBean(DomainConfig.BEAN_NAME_OBJECT_MAPPER, ObjectMapper.class); queryManager = context.getBean(QueryRestConfig.BEAN_NAME_QUERY_MANAGER, QueryManager.class); queryValidator = context.getBean(QueryRestConfig.BEAN_NAME_QUERY_VALIDATOR, Validator.class); + requestProviderValidator = context.getBean(QueryRestConfig.BEAN_NAME_REQUEST_PROVIDER_VALIDATOR, Validator.class); } @InitBinder @@ -146,8 +151,32 @@ public class QueryRestController implements ApplicationContextAware { value = DomainConfig.PATH_QUERY, method = RequestMethod.GET) public void executeQueryBodyAsString(@RequestParam String jsonBody, HttpServletResponse res) throws Exception { - DAQQuery query = objectMapper.readValue(jsonBody, DAQQuery.class); - executeQuery(query, res); + DAQQuery query; + try { + query = objectMapper.readValue(jsonBody, DAQQuery.class); + } catch (Exception e) { + // somehow only needed for our test mokup environment (@RequestParam seems to do the + // decoding on production server). + LOGGER.info("Could not parse '{}' due to '{}'. Retry url decoded.", jsonBody, e.getMessage()); + query = objectMapper.readValue(URLDecoder.decode(jsonBody, StandardCharsets.UTF_8.name()), DAQQuery.class); + } + + final Errors errors = new DirectFieldBindingResult(query, query.getClass().getName()); + if (requestProviderValidator.supports(query.getClass())) { + requestProviderValidator.validate(query, errors); + } + if (queryValidator.supports(query.getClass())) { + queryValidator.validate(query, errors); + } + + final List allErrors = errors.getAllErrors(); + if (allErrors.isEmpty()) { + executeQuery(query, res); + } else { + final String message = String.format("Could not parse '%s' due to '%s'.", jsonBody, errors.toString()); + LOGGER.error(message); + throw new IllegalStateException(message); + } } /** @@ -178,8 +207,32 @@ public class QueryRestController implements ApplicationContextAware { value = DomainConfig.PATH_QUERIES, method = RequestMethod.GET) public void executeQueriesBodyAsString(@RequestParam String jsonBody, HttpServletResponse res) throws Exception { - DAQQueries queries = objectMapper.readValue(jsonBody, DAQQueries.class); - executeQueries(queries, res); + DAQQueries queries; + try { + queries = objectMapper.readValue(jsonBody, DAQQueries.class); + } catch (Exception e) { + // somehow only needed for our test mokup environment (@RequestParam seems to do the + // decoding on production server). + LOGGER.info("Could not parse '{}' due to '{}'. Retry url decoded.", jsonBody, e.getMessage()); + queries = objectMapper.readValue(URLDecoder.decode(jsonBody, StandardCharsets.UTF_8.name()), DAQQueries.class); + } + + final Errors errors = new DirectFieldBindingResult(queries, queries.getClass().getName()); + if (requestProviderValidator.supports(queries.getClass())) { + requestProviderValidator.validate(queries, errors); + } + if (queryValidator.supports(queries.getClass())) { + queryValidator.validate(queries, errors); + } + + final List allErrors = errors.getAllErrors(); + if (allErrors.isEmpty()) { + executeQueries(queries, res); + } else { + final String message = String.format("Could not parse '%s' due to '%s'.", jsonBody, errors.toString()); + LOGGER.error(message); + throw new IllegalStateException(message); + } } /** diff --git a/src/main/resources/queryrest.properties b/src/main/resources/queryrest.properties index fbe21fc..b1a24c1 100644 --- a/src/main/resources/queryrest.properties +++ b/src/main/resources/queryrest.properties @@ -12,4 +12,4 @@ filestorage.reader.local=false filestorage.compaction.startup.init=false filestorage.startup.repair.init=false -# server.port=8081 \ No newline at end of file +#server.port=8081 \ No newline at end of file diff --git a/src/test/java/ch/psi/daq/test/queryrest/controller/CSVQueryRestControllerTest.java b/src/test/java/ch/psi/daq/test/queryrest/controller/CSVQueryRestControllerTest.java index 8390cd1..3982643 100644 --- a/src/test/java/ch/psi/daq/test/queryrest/controller/CSVQueryRestControllerTest.java +++ b/src/test/java/ch/psi/daq/test/queryrest/controller/CSVQueryRestControllerTest.java @@ -15,6 +15,7 @@ import java.util.Set; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import org.apache.http.client.utils.URIBuilder; import org.junit.After; import org.junit.Test; import org.springframework.http.MediaType; @@ -492,7 +493,7 @@ public class CSVQueryRestControllerTest extends AbstractDaqRestTest { assertEquals("" + TimeUtils.getMillis(TestTimeUtils.getTimeFromPulseId(pulse)), record.get(column++)); assertEquals("[8]", record.get(column++)); assertEquals("1", record.get(column++)); - assertTrue(record.get(column).startsWith("["+pulse+",")); + assertTrue(record.get(column).startsWith("[" + pulse + ",")); assertTrue(record.get(column++).endsWith("]")); } ++pulse; @@ -1020,6 +1021,11 @@ public class CSVQueryRestControllerTest extends AbstractDaqRestTest { String response = result.getResponse().getContentAsString(); System.out.println("Response: " + response); + checkDateRangeQueryBinSizeAggregate(channels, aggregations, queryFields, response); + } + + private void checkDateRangeQueryBinSizeAggregate(final List channels, final List aggregations, + final Set queryFields, final String response) throws Exception { CSVFormat csvFormat = CSVFormat.EXCEL.withDelimiter(CSVResponseStreamWriter.DELIMITER_CVS); StringReader reader = new StringReader(response); CSVParser csvParser = new CSVParser(reader, csvFormat); @@ -1073,6 +1079,61 @@ public class CSVQueryRestControllerTest extends AbstractDaqRestTest { } } + @Test + public void testDateRangeQueryBinSizeAggregate_GET() throws Exception { + List channels = Arrays.asList(TEST_CHANNEL_01); + long startTime = 0; + long endTime = 999; + String startDate = TimeUtils.format(startTime); + String endDate = TimeUtils.format(endTime); + List aggregations = new ArrayList<>(); + aggregations.add(Aggregation.min); + aggregations.add(Aggregation.mean); + aggregations.add(Aggregation.max); + + DAQQuery request = new DAQQuery( + new RequestRangeDate( + startDate, + endDate), + channels); + // request.setAggregation(new + // AggregationDescriptor().setDurationPerBin(100).setAggregations(aggregations)); + // make sure defaults are set + request.setAggregation(new AggregationDescriptor().setDurationPerBin(100)); + request.setResponse(new CSVHTTPResponse()); + + LinkedHashSet queryFields = new LinkedHashSet<>(); + queryFields.add(QueryField.channel); + queryFields.add(QueryField.pulseId); + queryFields.add(QueryField.iocSeconds); + queryFields.add(QueryField.iocMillis); + queryFields.add(QueryField.globalSeconds); + queryFields.add(QueryField.globalMillis); + queryFields.add(QueryField.shape); + queryFields.add(QueryField.eventCount); + request.setFields(queryFields); + + String content = mapper.writeValueAsString(request); + System.out.println(content); + + URIBuilder ub = new URIBuilder(DomainConfig.PATH_QUERY); + ub.addParameter(DomainConfig.PATH_QUERY_PARAM_JSON_BODY, content); + String url = ub.toString(); + System.out.println(url); + + MvcResult result = this.mockMvc + .perform(MockMvcRequestBuilders + .get(url)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andReturn(); + + String response = result.getResponse().getContentAsString(); + System.out.println("Response: " + response); + + checkDateRangeQueryBinSizeAggregate(channels, aggregations, queryFields, response); + } + @Test public void testQuery_NoTimeFields_01() throws Exception { List channels = Arrays.asList(TEST_CHANNEL_01, TEST_CHANNEL_02); diff --git a/src/test/java/ch/psi/daq/test/queryrest/response/ResponseQueryTest.java b/src/test/java/ch/psi/daq/test/queryrest/response/ResponseQueryTest.java index c3ce36f..68ac2d1 100644 --- a/src/test/java/ch/psi/daq/test/queryrest/response/ResponseQueryTest.java +++ b/src/test/java/ch/psi/daq/test/queryrest/response/ResponseQueryTest.java @@ -19,17 +19,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import ch.psi.daq.domain.backend.Backend; import ch.psi.daq.domain.config.DomainConfig; import ch.psi.daq.domain.query.DAQQuery; -import ch.psi.daq.domain.query.operation.Aggregation; -import ch.psi.daq.domain.query.operation.AggregationDescriptor; import ch.psi.daq.domain.query.operation.Compression; import ch.psi.daq.domain.query.response.Response; -import ch.psi.daq.domain.request.range.RequestRangeDate; import ch.psi.daq.domain.request.range.RequestRangePulseId; import ch.psi.daq.queryrest.response.csv.CSVHTTPResponse; import ch.psi.daq.queryrest.response.json.JSONHTTPResponse; import ch.psi.daq.test.queryrest.AbstractDaqRestTest; -public class ResponseQueryTest extends AbstractDaqRestTest{ +public class ResponseQueryTest extends AbstractDaqRestTest { @Resource(name = DomainConfig.BEAN_NAME_BACKEND_DEFAULT) private Backend backend; @@ -74,26 +71,6 @@ public class ResponseQueryTest extends AbstractDaqRestTest{ assertEquals(query.getResponse().getCompression().getFileSuffix(), deserial.getResponse().getFileSuffix()); } - @Test - public void test_JSON_02_01() throws JsonParseException, JsonMappingException, IOException { - DAQQuery query = new DAQQuery( - new RequestRangeDate( - "2017-09-25T19:00:05.319+02:00", - "2017-09-25T19:03:05.319+02:00"), - new AggregationDescriptor().setDurationPerBin(100), - "TestChannel_01"); - query.setResponse(new CSVHTTPResponse(Compression.GZIP)); - -// String value = mapper.writeValueAsString(query); - String value = "{\"channels\":[{\"backend\":\"queryrest-1\",\"name\":\"TestChannel_01\"}],\"fields\":[\"globalMillis\",\"globalDate\",\"value\",\"shape\",\"eventCount\"],\"range\":{\"startDate\":\"2017-09-25T19:02:57.318+02:00\",\"endDate\":\"2017-09-25T19:03:05.319+02:00\"},\"aggregation\":{\"durationPerBin\":\"PT15M\"},\"response\":{\"format\":\"csv\",\"compression\":\"none\"}}"; -// System.out.println(value); - DAQQuery deserial = mapper.readValue(value, DAQQuery.class); - - assertNotNull(deserial.getResponse()); - assertEquals(query.getResponse().getClass(), deserial.getResponse().getClass()); - assertEquals(query.getResponse().getCompression(), deserial.getResponse().getCompression()); - } - @Test public void test_JSON_03() throws JsonParseException, JsonMappingException, IOException { DAQQuery query = new DAQQuery(