Files
perfdb/pdb-ui/src/main/java/org/lucares/pdbui/PdbController.java
Andreas Huber c1974d21b2 replace startDate + dateRange with start and end date
The new datetimepicker can be used to specify date ranges. We no longer
need to define a start date and a range. This simplifies the code
for zooming and shifting considerably.
2018-08-11 17:45:20 +02:00

269 lines
9.8 KiB
Java

package org.lucares.pdbui;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang3.StringUtils;
import org.lucares.pdb.datastore.Proposal;
import org.lucares.pdb.plot.api.AxisScale;
import org.lucares.pdb.plot.api.Limit;
import org.lucares.pdb.plot.api.PlotSettings;
import org.lucares.pdbui.domain.Aggregate;
import org.lucares.pdbui.domain.AutocompleteProposal;
import org.lucares.pdbui.domain.AutocompleteProposalByValue;
import org.lucares.pdbui.domain.AutocompleteResponse;
import org.lucares.pdbui.domain.PlotRequest;
import org.lucares.pdbui.domain.PlotResponse;
import org.lucares.pdbui.domain.PlotResponseStats;
import org.lucares.performance.db.PerformanceDb;
import org.lucares.recommind.logs.InternalPlottingException;
import org.lucares.recommind.logs.NoDataPointsException;
import org.lucares.recommind.logs.PlotResult;
import org.lucares.recommind.logs.Plotter;
import org.lucares.utils.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@Controller
@EnableAutoConfiguration
public class PdbController implements HardcodedValues, PropertyKeys {
private static final Logger LOGGER = LoggerFactory.getLogger(PdbController.class);
private final Plotter plotter;
private final PerformanceDb db;
private final ReentrantLock plotterLock = new ReentrantLock();
@Value("${" + PRODUCTION_MODE + ":true}")
private boolean modeProduction;
public PdbController(final PerformanceDb db, final Plotter plotter) {
this.db = db;
this.plotter = plotter;
}
@GetMapping("/")
public ModelAndView index() {
final String view = "main";
final Map<String, Object> model = new HashMap<>();
// model.put("oldestValue",
// LocalDateTime.now().minusDays(7).format(DATE_FORMAT_BEGIN));
// model.put("latestValue", LocalDateTime.now().format(DATE_FORMAT_END));
model.put("isProduction", modeProduction);
return new ModelAndView(view, model);
}
@RequestMapping(path = "/plots", //
method = RequestMethod.GET, //
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
)
@ResponseBody
ResponseEntity<PlotResponse> createPlotGet(@RequestParam(name = "request") final String request)
throws InternalPlottingException, InterruptedException, JsonParseException, JsonMappingException,
IOException {
final ObjectMapper objectMapper = new ObjectMapper();
final PlotRequest plotRequest = objectMapper.readValue(request, PlotRequest.class);
return createPlot(plotRequest);
}
@RequestMapping(path = "/plots", //
method = RequestMethod.POST, //
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
)
@ResponseBody
ResponseEntity<PlotResponse> createPlot(@RequestBody final PlotRequest request)
throws InternalPlottingException, InterruptedException {
final PlotSettings plotSettings = PlotSettingsTransformer.toSettings(request);
if (StringUtils.isBlank(plotSettings.getQuery())) {
throw new BadRequest("The query must not be empty!");
}
// TODO the UI should cancel requests that are in flight before sending a plot
// request
if (plotterLock.tryLock(5, TimeUnit.SECONDS)) {
try {
final PlotResult result = plotter.plot(plotSettings);
final String imageUrl = WEB_IMAGE_OUTPUT_PATH + "/" + result.getImageName();
LOGGER.trace("image url: {}", imageUrl);
final String thumbnailUrl = result.getThumbnailPath() != null
? WEB_IMAGE_OUTPUT_PATH + "/" + result.getThumbnailName()
: "img/no-thumbnail.png";
final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries());
final PlotResponse plotResponse = new PlotResponse(stats, imageUrl, thumbnailUrl);
return ResponseEntity.ok().body(plotResponse);
} catch (final NoDataPointsException e) {
throw new NotFoundException(e);
} finally {
plotterLock.unlock();
}
} else {
throw new ServiceUnavailableException("Too many parallel requests!");
}
}
@RequestMapping(path = "/plots", //
method = RequestMethod.GET, //
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE //
)
StreamingResponseBody createPlotImage(@RequestParam(name = "query", defaultValue = "") final String query,
@RequestParam(name = "groupBy[]", defaultValue = "") final List<String> aGroupBy,
@RequestParam(name = "limitBy.number", defaultValue = "10") final int limit,
@RequestParam(name = "limitBy.selected", defaultValue = "NO_LIMIT") final Limit limitBy,
@RequestParam(name = "dateRange") final String dateRange,
@RequestParam(name = "axisScale", defaultValue = "LINEAR") final AxisScale axisScale,
@RequestParam(name = "aggregate", defaultValue = "NONE") final Aggregate aggregate,
@RequestParam(name = "keyOutside", defaultValue = "false") final boolean keyOutside,
@RequestParam(name = "width", defaultValue = "1920") final int hidth,
@RequestParam(name = "height", defaultValue = "1080") final int height) {
return (final OutputStream outputStream) -> {
if (StringUtils.isBlank(query)) {
throw new BadRequest("The query must not be empty!");
}
if (StringUtils.isBlank(dateRange)) {
throw new BadRequest("The parameter 'dateRange' must be set.");
}
final PlotSettings plotSettings = new PlotSettings();
plotSettings.setQuery(query);
plotSettings.setGroupBy(aGroupBy);
plotSettings.setHeight(height);
plotSettings.setWidth(hidth);
plotSettings.setLimit(limit);
plotSettings.setLimitBy(limitBy);
plotSettings.setDateRange(dateRange);
plotSettings.setYAxisScale(axisScale);
plotSettings.setAggregate(PlotSettingsTransformer.toAggregateInternal(aggregate));
plotSettings.setKeyOutside(keyOutside);
plotSettings.setGenerateThumbnail(false);
if (plotterLock.tryLock()) {
try {
final PlotResult result = plotter.plot(plotSettings);
try (FileInputStream in = new FileInputStream(result.getImagePath().toFile())) {
StreamUtils.copy(in, outputStream);
}
} catch (final NoDataPointsException e) {
throw new NotFoundException(e);
} catch (final InternalPlottingException e) {
throw new InternalServerError(e);
} finally {
plotterLock.unlock();
}
} else {
throw new ServiceUnavailableException("Too many parallel requests!");
}
};
}
@RequestMapping(path = "/autocomplete", //
method = RequestMethod.GET, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
)
@ResponseBody
AutocompleteResponse autocomplete(@RequestParam(name = "query") final String query,
@RequestParam(name = "caretIndex") final int caretIndex) {
final AutocompleteResponse result = new AutocompleteResponse();
final int zeroBasedCaretIndex = caretIndex - 1;
final List<Proposal> proposals = db.autocomplete(query, zeroBasedCaretIndex);
final List<Proposal> nonEmptyProposals = CollectionUtils.filter(proposals, p -> p.hasResults());
final List<AutocompleteProposal> autocompleteProposals = toAutocompleteProposals(nonEmptyProposals);
Collections.sort(autocompleteProposals, new AutocompleteProposalByValue());
result.setProposals(autocompleteProposals);
return result;
}
@RequestMapping(path = "/fields", //
method = RequestMethod.GET, //
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
)
@ResponseBody
List<String> fields() {
final List<String> fields = db.getFields();
fields.sort(Collator.getInstance(Locale.ENGLISH));
return fields;
}
@RequestMapping(path = "/fields/{fieldName}/values", //
method = RequestMethod.GET, //
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //
produces = MediaType.APPLICATION_JSON_UTF8_VALUE //
)
@ResponseBody
SortedSet<String> fields(@PathVariable(name = "fieldName") final String fieldName,
@RequestParam(name = "query") final String query) {
final SortedSet<String> fields = db.getFieldsValues(query, fieldName);
return fields;
}
private List<AutocompleteProposal> toAutocompleteProposals(final List<Proposal> proposals) {
final List<AutocompleteProposal> result = new ArrayList<>();
for (final Proposal proposal : proposals) {
final AutocompleteProposal e = new AutocompleteProposal();
e.setValue(proposal.getProposedTag());
e.setNewQuery(proposal.getNewQuery());
e.setNewCaretPosition(proposal.getNewCaretPosition());
result.add(e);
}
return result;
}
}