/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.importer.web;
import static org.geoserver.importer.web.ImporterWebUtils.importer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.model.util.ListModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.util.time.Duration;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.importer.BasicImportFilter;
import org.geoserver.importer.Database;
import org.geoserver.importer.Directory;
import org.geoserver.importer.FileData;
import org.geoserver.importer.ImportContext;
import org.geoserver.importer.ImportData;
import org.geoserver.importer.ImportTask;
import org.geoserver.importer.RasterFormat;
import org.geoserver.importer.VectorFormat;
import org.geoserver.importer.job.Task;
import org.geoserver.web.GeoServerSecuredPage;
import org.geoserver.web.wicket.GeoServerDialog;
import org.geoserver.web.wicket.GeoServerDialog.DialogDelegate;
import org.geoserver.web.wicket.Icon;
public class ImportPage extends GeoServerSecuredPage {
GeoServerDialog dialog;
AtomicBoolean running = new AtomicBoolean(false);
public ImportPage(PageParameters pp) {
this(new ImportContextModel(pp.get("id").toLong()));
}
public ImportPage(ImportContext imp) {
this(new ImportContextModel(imp));
}
public ImportPage(IModel<ImportContext> model) {
initComponents(model);
}
void initComponents(final IModel<ImportContext> model) {
add(new Label("id", new PropertyModel(model, "id")));
ImportContextProvider provider = new ImportContextProvider() {
@Override
protected List<Property<ImportContext>> getProperties() {
return Arrays.asList(STATE, CREATED, UPDATED);
}
@Override
protected List<ImportContext> getItems() {
return Collections.singletonList(model.getObject());
}
};
add(new AjaxLink("raw") {
@Override
public void onClick(AjaxRequestTarget target) {
dialog.setInitialHeight(500);
dialog.setInitialWidth(700);
dialog.showOkCancel(target, new DialogDelegate() {
@Override
protected Component getContents(String id) {
XStreamPersister xp = importer()
.createXStreamPersisterXML();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
xp.save(model.getObject(), bout);
} catch (IOException e) {
bout = new ByteArrayOutputStream();
LOGGER.log(Level.FINER, e.getMessage(), e);
e.printStackTrace(new PrintWriter(bout));
}
return new TextAreaPanel(id, new Model(new String(bout
.toByteArray())));
}
@Override
protected boolean onSubmit(AjaxRequestTarget target,
Component contents) {
return true;
}
});
}
}.setVisible(ImporterWebUtils.isDevMode()));
final ImportContextTable headerTable = new ImportContextTable("header", provider);
headerTable.setOutputMarkupId(true);
headerTable.setFilterable(false);
headerTable.setPageable(false);
add(headerTable);
final ImportContext imp = model.getObject();
boolean selectable = imp.getState() != ImportContext.State.COMPLETE;
final ImportTaskTable taskTable = new ImportTaskTable("tasks",
new ImportTaskProvider(model), selectable) {
@Override
protected void onSelectionUpdate(AjaxRequestTarget target) {
updateImportLink((AjaxLink) ImportPage.this.get("import"), this, target);
}
}.setFeedbackPanel(feedbackPanel);
taskTable.setOutputMarkupId(true);
taskTable.setFilterable(false);
add(taskTable);
final AjaxLink<Long> importLink = new AjaxLink<Long>("import",
new Model<Long>()) {
@Override
protected void disableLink(ComponentTag tag) {
super.disableLink(tag);
ImporterWebUtils.disableLink(tag);
}
@Override
public void onClick(AjaxRequestTarget target) {
ImportContext imp = model.getObject();
BasicImportFilter filter = new BasicImportFilter();
for (ImportTask t : taskTable.getSelection()) {
filter.add(t);
}
//set running flag and update cancel link
running.set(true);
target.add(cancelLink(this));
final Long jobid = importer().runAsync(imp, filter, false);
setDefaultModelObject(jobid);
final AjaxLink self = this;
// create a timer to update the table and reload the page when
// necessary
taskTable.add(new AbstractAjaxTimerBehavior(Duration.milliseconds(500)) {
@Override
protected void onTimer(AjaxRequestTarget target) {
Task<ImportContext> job = importer().getTask(jobid);
if (job == null || job.isDone()) {
// remove the timer
stop(null);
self.setEnabled(true);
target.add(self);
running.set(false);
target.add(cancelLink(self));
/*ImportContext imp = model.getObject();
if (imp.getState() == ImportContext.State.COMPLETE) {
// enable cancel, which will not be "done"
setLinkEnabled(cancelLink(self), true, target);
} else {
// disable cancel, import is not longer running, but
// also
// not complete
setLinkEnabled(cancelLink(self), false, target);
}*/
}
// update the table
target.add(taskTable);
target.add(headerTable);
}
});
target.add(taskTable);
// disable import button
setLinkEnabled(this, false, target);
// enable cancel button
//setLinkEnabled(cancelLink(this), true, target);
}
};
importLink.setOutputMarkupId(true);
importLink.setEnabled(doSelectReady(imp, taskTable, null));
add(importLink);
final AjaxLink cancelLink = new AjaxLink("cancel") {
@Override
protected void disableLink(ComponentTag tag) {
super.disableLink(tag);
ImporterWebUtils.disableLink(tag);
}
@Override
public void onClick(AjaxRequestTarget target) {
ImportContext imp = model.getObject();
if (!running.get()) {
//if (imp.getState() == ImportContext.State.COMPLETE) {
setResponsePage(ImportDataPage.class);
return;
}
Long jobid = importLink.getModelObject();
if (jobid == null) {
return;
}
Task<ImportContext> task = importer().getTask(jobid);
if (task == null || task.isDone()) {
return;
}
task.getMonitor().setCanceled(true);
task.cancel(false);
try {
task.get();
} catch (Exception e) {
}
// enable import button
setLinkEnabled(importLink, true, target);
// disable cancel button
//setLinkEnabled(cancelLink(importLink), false, target);
}
};
//cancelLink.setEnabled(imp.getState() == ImportContext.State.COMPLETE);
cancelLink.add(new Label("text", new CancelTitleModel()));
add(cancelLink);
WebMarkupContainer selectPanel = new WebMarkupContainer("select");
selectPanel.add(new AjaxLink<ImportContext>("select-all", model) {
@Override
public void onClick(AjaxRequestTarget target) {
taskTable.selectAll();
target.add(taskTable);
updateImportLink(importLink, taskTable, target);
}
});
selectPanel.add(new AjaxLink<ImportContext>("select-none", model) {
@Override
public void onClick(AjaxRequestTarget target) {
taskTable.clearSelection();
target.add(taskTable);
updateImportLink(importLink, taskTable, target);
}
});
selectPanel.add(new AjaxLink<ImportContext>("select-ready", model) {
@Override
public void onClick(AjaxRequestTarget target) {
doSelectReady(getModelObject(), taskTable, target);
target.add(taskTable);
updateImportLink(importLink, taskTable, target);
}
});
add(selectPanel);
add(new Icon("icon",new DataIconModel(imp.getData())));
add(new Label("title", new DataTitleModel(imp))
.add(new AttributeModifier("title", new DataTitleModel(imp, false))));
add(dialog = new GeoServerDialog("dialog"));
}
void updateImportLink(AjaxLink link, ImportTaskTable table, AjaxRequestTarget target) {
boolean enable = !table.getSelection().isEmpty();
if (enable) {
boolean allComplete = true;
for (ImportTask task : table.getSelection()) {
allComplete = task.getState() == ImportTask.State.COMPLETE;
}
enable = !allComplete;
}
setLinkEnabled(link, enable, target);
}
void setLinkEnabled(AjaxLink link, boolean enabled, AjaxRequestTarget target) {
link.setEnabled(enabled);
target.add(link);
}
AjaxLink cancelLink(AjaxLink importLink) {
return (AjaxLink) importLink.getParent().get("cancel");
}
boolean doSelectReady(ImportContext imp, ImportTaskTable table, AjaxRequestTarget target) {
boolean empty = true;
for (ImportTask t : imp.getTasks()) {
if (t.getState() == ImportTask.State.READY) {
table.selectObject(t);
empty = false;
}
}
return empty;
}
@Override
public String getAjaxIndicatorMarkupId() {
return null;
}
static class DataIconModel extends LoadableDetachableModel<PackageResourceReference> {
ImportData data;
public DataIconModel(ImportData data) {
this.data = data;
}
@Override
protected PackageResourceReference load() {
DataIcon icon = null;
if (data instanceof FileData) {
FileData df = (FileData) data;
if (data instanceof Directory) {
icon = DataIcon.FOLDER;
}
else {
icon = df.getFormat() instanceof VectorFormat ? DataIcon.FILE_VECTOR :
df.getFormat() instanceof RasterFormat ? DataIcon.FILE_RASTER : DataIcon.FILE;
}
}
else if (data instanceof Database) {
icon = DataIcon.DATABASE;
}
else {
icon = DataIcon.VECTOR; //TODO: better default
}
return icon.getIcon();
}
}
static class DataTitleModel extends LoadableDetachableModel<String> {
long contextId;
boolean abbrev;
DataTitleModel(ImportContext imp) {
this(imp, true);
}
DataTitleModel(ImportContext imp, boolean abbrev) {
this.contextId = imp.getId();
this.abbrev = abbrev;
}
@Override
protected String load() {
ImportContext ctx = importer().getContext(contextId);
ImportData data = ctx.getData();
String title = data != null ? data.toString() : ctx.toString();
if (abbrev && title.length() > 70) {
//shorten it
title = title.substring(0,20) + "[...]" + title.substring(title.length()-50);
}
return title;
}
}
class CancelTitleModel implements IModel<String> {
@Override
public String getObject() {
StringResourceModel m = running.get() ?
new StringResourceModel("cancel", new Model("Cancel")) :
new StringResourceModel("done", new Model("Done"));
return m.getString();
}
@Override
public void setObject(String object) {
}
@Override
public void detach() {
}
}
static class TextAreaPanel extends Panel {
public TextAreaPanel(String id, IModel textAreaModel) {
super(id);
add(new TextArea("textArea", textAreaModel));
}
}
static class FilteredImportTasksModel extends ListModel<ImportTask> {
IModel<List<ImportTask>> taskModel;
boolean empty;
FilteredImportTasksModel(IModel<List<ImportTask>> taskModel, boolean empty) {
this.taskModel = taskModel;
this.empty = empty;
}
@Override
public List<ImportTask> getObject() {
List<ImportTask> tasks = new ArrayList();
for (ImportTask task : taskModel.getObject()) {
tasks.add(task);
}
return tasks;
}
@Override
public void detach() {
super.detach();
taskModel.detach();
}
}
}