/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.cyclop.web.panels.queryimport;
import java.io.ByteArrayInputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.PageableListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.util.lang.Bytes;
import org.cyclop.common.AppConfig;
import org.cyclop.model.UserPreferences;
import org.cyclop.service.importer.QueryImporter;
import org.cyclop.service.importer.model.ImportConfig;
import org.cyclop.service.importer.model.ImportStats;
import org.cyclop.service.um.UserManager;
import org.cyclop.web.common.ImmutableListModel;
import org.cyclop.web.common.JsFunctionBuilder;
import org.cyclop.web.components.pagination.BootstrapPagingNavigator;
import org.cyclop.web.components.pagination.PagerConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** @author Maciej Miklas */
public class QueryImportPanel extends Panel {
private final static Logger LOG = LoggerFactory.getLogger(QueryImportPanel.class);
private static final JavaScriptResourceReference JS_IMPORT = new JavaScriptResourceReference(
QueryImportPanel.class, "queryImport.js");
private ImmutableListModel<ImportResult> resultModel;
private AppConfig conf = AppConfig.get();
private WebMarkupContainer importResultContainer;
@Inject
@Named(QueryImporter.IMPL_SERIAL)
private QueryImporter serialImporter;
@Inject
@Named(QueryImporter.IMPL_PARALLEL)
private QueryImporter parallelImporter;
@Inject
private UserManager um;
private ThreadLocal<DecimalFormat> NUMBER_FORMAT = new ThreadLocal<DecimalFormat>() {
protected DecimalFormat initialValue() {
DecimalFormat nf = (DecimalFormat) NumberFormat.getNumberInstance(WebSession.get().getLocale());
nf.applyPattern("###.###");
return nf;
};
};
public QueryImportPanel(String id) {
super(id);
}
@Override
protected void onInitialize() {
super.onInitialize();
ImportOptions importOptions = createImportOptions();
setDefaultModel(new CompoundPropertyModel<>(importOptions));
Form<?> form = initFileUpload(importOptions);
initUploadOptions(form);
importResultContainer = initImportResultContainer();
resultModel = initResultTable(importResultContainer);
}
private ImportOptions createImportOptions() {
ImportOptions importOptions = new ImportOptions();
UserPreferences prefs = um.readPreferences();
importOptions.setContinueWithErrors(prefs.isImportContinueWithErrors());
importOptions.setIncludeInHistory(prefs.isImportIncludeInHistory());
importOptions.setParallel(prefs.isImportParallel());
return importOptions;
}
private String createStatsMessage(ImportStats stats) {
StringBuilder buf = new StringBuilder();
DecimalFormat nf = NUMBER_FORMAT.get();
buf.append("Executed ").append(nf.format(stats.errorCount + stats.successCount)).append(" queries in ")
.append(stats.runtime.toString()).append(", ").append(nf.format(stats.successCount))
.append(" were succesfull, ").append(nf.format(stats.errorCount)).append(" failed");
String resp = buf.toString();
LOG.info(resp);
return resp;
}
private void executeImport(AjaxRequestTarget target, ImportOptions importOptions, FileUpload upload) {
byte[] fileContentBytes = upload.getBytes();
LOG.debug("Importing file of {} bytes", fileContentBytes.length);
ImportResultWriter result = new ImportResultWriter();
ImportConfig config = createImportConfig(importOptions);
ImportStats stats = getImporter(importOptions).importScript(new ByteArrayInputStream(fileContentBytes), result,
config);
resultModel.setObject(result.getResult());
String resp = createStatsMessage(stats);
sendJsResponse(target, resp);
importResultContainer.setVisible(true);
target.add(importResultContainer);
updatePreferences(importOptions);
}
private ImportConfig createImportConfig(ImportOptions importOptions) {
ImportConfig config = new ImportConfig();
config.withContinueWithErrors(importOptions.isContinueWithErrors()).withUpdateHistory(
importOptions.isContinueWithErrors());
return config;
}
private QueryImporter getImporter(ImportOptions importOptions) {
return importOptions.isParallel() ? parallelImporter : serialImporter;
}
private Form<?> initFileUpload(final ImportOptions importOptions) {
final FileUploadField scriptFile = new FileUploadField("scriptFile");
final Form<?> form = new Form<Void>("form") {
@Override
protected void onSubmit() {
}
};
form.setMaxSize(Bytes.megabytes(conf.queryImport.maxFileSizeMb));
add(form);
form.add(scriptFile);
form.add(new AjaxButton("ajaxSubmit") {
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
LOG.info("Got error on import upload: {}", form);
sendJsResponse(target, ErrorCodes.FILE_SIZE_LIMIT.name());
}
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
FileUpload upload = scriptFile.getFileUpload();
if (upload == null) {
return;
}
executeImport(target, importOptions, upload);
}
});
return form;
}
private WebMarkupContainer initImportResultContainer() {
WebMarkupContainer historyContainer = new WebMarkupContainer("importResultContainer");
historyContainer.setOutputMarkupPlaceholderTag(true);
historyContainer.setVisible(false);
add(historyContainer);
return historyContainer;
}
private ImmutableListModel<ImportResult> initResultTable(final WebMarkupContainer container) {
ImmutableListModel<ImportResult> model = new ImmutableListModel<>();
PageableListView<ImportResult> historyTable = new PageableListView<ImportResult>("resultRow", model, 1) {
@Override
protected void populateItem(ListItem<ImportResult> item) {
ImportResult entry = item.getModel().getObject();
if (entry == null) {
return;
}
populateRuntime(item, entry);
populateQuery(item, entry);
}
};
container.add(historyTable);
final BootstrapPagingNavigator importResultPager = new BootstrapPagingNavigator("importResultPager",
historyTable, new PagerConfigurator() {
@Override
public void onItemsPerPageChanged(AjaxRequestTarget target, long newItemsPerPage) {
UserPreferences prefs = um.readPreferences().setPagerImportItems(newItemsPerPage);
um.storePreferences(prefs);
}
@Override
public long getInitialItemsPerPage() {
return um.readPreferences().getPagerImportItems();
}
});
container.add(importResultPager);
model.registerOnChangeListener(o -> importResultPager.reset());
return model;
}
private void initUploadOptions(Form<?> form) {
CheckBox continueWithErrors = new CheckBox("continueWithErrors");
form.add(continueWithErrors);
CheckBox includeInHistory = new CheckBox("includeInHistory");
form.add(includeInHistory);
CheckBox parallel = new CheckBox("parallel");
form.add(parallel);
}
private void populateQuery(ListItem<ImportResult> item, ImportResult entry) {
Label queryLabel = new Label("query", entry.query.toDisplayString());
queryLabel.setEscapeModelStrings(false);
item.add(queryLabel);
WebMarkupContainer errorContainer = new WebMarkupContainer("queryError");
item.add(errorContainer);
if (entry.error == null) {
errorContainer.setVisible(false);
} else {
Label errorLabel = new Label("error", entry.error);
errorContainer.add(errorLabel);
}
}
private void populateRuntime(ListItem<ImportResult> item, ImportResult result) {
String dateStr = Long.toString(result.runTime);
Label executedOn = new Label("runtime", dateStr);
item.add(executedOn);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptReferenceHeaderItem.forReference(JS_IMPORT));
}
private void sendJsResponse(AjaxRequestTarget target, String response) {
String js = JsFunctionBuilder.function("onQueryImportResponse").param(response).build();
target.appendJavaScript(js);
}
private void updatePreferences(ImportOptions importOptions) {
UserPreferences prefs = um.readPreferences();
prefs.setImportContinueWithErrors(importOptions.isContinueWithErrors())
.setImportIncludeInHistory(importOptions.isIncludeInHistory())
.setImportParallel(importOptions.isParallel());
um.storePreferences(prefs);
}
}