/* * 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); } }