/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geogig.geoserver.web.repository;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager;
import org.locationtech.geogig.plumbing.TransactionBegin;
import org.locationtech.geogig.porcelain.ConfigOp;
import org.locationtech.geogig.porcelain.ConfigOp.ConfigAction;
import org.locationtech.geogig.porcelain.ConfigOp.ConfigScope;
import org.locationtech.geogig.porcelain.RemoteAddOp;
import org.locationtech.geogig.porcelain.RemoteListOp;
import org.locationtech.geogig.porcelain.RemoteRemoveOp;
import org.locationtech.geogig.repository.Context;
import org.locationtech.geogig.repository.IndexInfo;
import org.locationtech.geogig.repository.Remote;
import org.locationtech.geogig.repository.Repository;
import org.locationtech.geogig.repository.impl.GeogigTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
*/
public abstract class RepositoryEditFormPanel extends Panel {
private static final long serialVersionUID = -9001389048321749075L;
private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryEditFormPanel.class);
private RemotesListPanel remotes;
private ConfigListPanel localConfig;
private ConfigListPanel globalConfig;
private IndexListPanel indexes;
private ModalWindow popupWindow;
private Form<RepositoryInfo> form;
private boolean isNew = false;
public RepositoryEditFormPanel(final String id) {
this(id, null);
}
public RepositoryEditFormPanel(final String id, @Nullable IModel<RepositoryInfo> repoInfo) {
super(id);
isNew = repoInfo == null;
if (isNew) {
repoInfo = new Model<>(new RepositoryInfo());
}
setDefaultModel(repoInfo);
popupWindow = new ModalWindow("popupWindow");
add(popupWindow);
form = new Form<>("repoForm", repoInfo);
form.add(new RepositoryEditPanel("repo", repoInfo, isNew));
form.add(addRemoteLink());
List<RemoteInfo> remoteInfos = loadRemoteInfos(repoInfo.getObject());
form.add(remotes = new RemotesListPanel("remotes", remoteInfos));
form.add(addConfigLink(false));
Map<String, String> localConfigMap = loadConfig(repoInfo.getObject(), false);
form.add(localConfig = new ConfigListPanel("localConfig", localConfigMap));
form.add(addConfigLink(true));
Map<String, String> globalConfigMap = loadConfig(repoInfo.getObject(), true);
form.add(globalConfig = new ConfigListPanel("globalConfig", globalConfigMap));
List<IndexInfo> indexInfos = loadIndexes(repoInfo.getObject());
form.add(indexes = new IndexListPanel("indexes", indexInfos));
add(form);
FeedbackPanel feedback = new FeedbackPanel("feedback");
form.add(feedback);
form.add(new AjaxLink<Void>("cancel") {
private static final long serialVersionUID = 6220299771769708060L;
@Override
public void onClick(AjaxRequestTarget target) {
cancelled(target);
}
});
form.add(new AjaxSubmitLink("save", form) {
private static final long serialVersionUID = 1L;
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
target.add(form);
}
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
try {
RepositoryInfo repoInfo = (RepositoryInfo) form.getModelObject();
onSave(repoInfo, target);
} catch (IllegalArgumentException e) {
form.error(e.getMessage());
target.add(form);
}
}
});
}
private List<RemoteInfo> loadRemoteInfos(RepositoryInfo repo) {
String repoId = repo.getId();
if (null == repoId) {
return new ArrayList<>();
}
ArrayList<RemoteInfo> list = new ArrayList<>();
Repository geogig;
try {
geogig = RepositoryManager.get().getRepository(repoId);
if (geogig != null) {
ImmutableList<Remote> geogigRemotes = geogig.command(RemoteListOp.class).call();
list = RemoteInfo.fromList(geogigRemotes);
}
} catch (Exception e) {
LOGGER.warn("Failed to load Remotes for repository", e);
}
return list;
}
private Map<String, String> loadConfig(RepositoryInfo repo, boolean global) {
String repoId = repo.getId();
if (null == repoId) {
return Maps.newHashMap();
}
Repository geogig;
try {
geogig = RepositoryManager.get().getRepository(repoId);
if (geogig != null) {
Optional<Map<String, String>> config = geogig.command(ConfigOp.class)//
.setAction(ConfigAction.CONFIG_LIST)//
.setScope(global ? ConfigScope.GLOBAL : ConfigScope.LOCAL)//
.call();
if (config.isPresent()) {
return config.get();
}
}
} catch (Exception e) {
LOGGER.warn("Failed to load config for repository", e);
}
return Maps.newHashMap();
}
private List<IndexInfo> loadIndexes(RepositoryInfo repo) {
String repoId = repo.getId();
if (null == repoId) {
return Lists.newArrayList();
}
Repository geogig;
try {
geogig = RepositoryManager.get().getRepository(repoId);
return geogig.indexDatabase().getIndexInfos();
} catch (Exception e) {
LOGGER.warn("Failed to load indexes for repository", e);
}
return Lists.newArrayList();
}
private Component addRemoteLink() {
return new AjaxLink<Void>("addRemote") {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
RemoteInfo ri = new RemoteInfo();
IModel<RemoteInfo> model = new Model<>(ri);
RemotesListPanel table = RepositoryEditFormPanel.this.remotes;
RemoteEditPanel editPanel = new RemoteEditPanel(popupWindow.getContentId(), model,
popupWindow, table);
popupWindow.setContent(editPanel);
popupWindow.setTitle(new ResourceModel("RemoteEditPanel.title"));
popupWindow.show(target);
}
};
}
private Component addConfigLink(boolean global) {
return new AjaxLink<Void>(global ? "addGlobalConfig" : "addLocalConfig") {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
ConfigEntry config = new ConfigEntry();
IModel<ConfigEntry> model = new Model<>(config);
ConfigListPanel table = global ? RepositoryEditFormPanel.this.globalConfig : RepositoryEditFormPanel.this.localConfig;
ConfigEditPanel editPanel = new ConfigEditPanel(popupWindow.getContentId(), model,
popupWindow, table);
popupWindow.setContent(editPanel);
popupWindow.setTitle(new ResourceModel("ConfigEditPanel.title"));
popupWindow.show(target);
}
};
}
private void onSave(RepositoryInfo repoInfo, AjaxRequestTarget target) {
RepositoryManager manager = RepositoryManager.get();
// update remotes
Repository geogig;
try {
repoInfo = manager.save(repoInfo);
geogig = manager.getRepository(repoInfo.getId());
} catch (Exception e) {
form.error("Unable to connect to repository " + repoInfo.getLocation() + "\n"
+ e.getMessage());
target.add(form);
return;
}
Function<RemoteInfo, Integer> keyFunction = new Function<RemoteInfo, Integer>() {
@Override
public Integer apply(RemoteInfo r) {
return r.getId();
}
};
Map<Integer, RemoteInfo> currentRemotes = new HashMap<>(
Maps.uniqueIndex(loadRemoteInfos(repoInfo), keyFunction));
Set<RemoteInfo> newRemotes = Sets.newHashSet(remotes.getRemotes());
if (!currentRemotes.isEmpty() || !newRemotes.isEmpty()) {
GeogigTransaction tx = geogig.command(TransactionBegin.class).call();
try {
updateRemotes(tx, currentRemotes, newRemotes);
tx.commit();
} catch (Exception e) {
try {
tx.abort();
} finally {
form.error(e.getMessage());
target.add(form);
}
return;
}
}
Map<String, String> currentLocalConfig = loadConfig(repoInfo, false);
updateConfig(geogig.context(), currentLocalConfig, Lists.newArrayList(localConfig.getConfigs()), false);
Map<String, String> currentGlobalConfig = loadConfig(repoInfo, true);
updateConfig(geogig.context(), currentGlobalConfig, Lists.newArrayList(globalConfig.getConfigs()), true);
saved(repoInfo, target);
}
private void updateRemotes(Context geogig, Map<Integer, RemoteInfo> currentRemotes,
Set<RemoteInfo> newRemotes) throws Exception {
// handle deletes first, in case a remote was deleted in the table and then a new one added
// with the same name
{
Map<Integer, RemoteInfo> remaining = new HashMap<>();
for (RemoteInfo ri : newRemotes) {
if (ri.getId() != null) {
remaining.put(ri.getId(), ri);
}
}
for (RemoteInfo deleted : currentRemotes.values()) {
if (!remaining.containsKey(deleted.getId())) {
String name = deleted.getName();
try {
geogig.command(RemoteRemoveOp.class).setName(name).call();
} catch (RuntimeException e) {
throw new RuntimeException("Error deleting remote " + name, e);
}
}
}
}
for (RemoteInfo ri : newRemotes) {
if (ri.getId() == null) {// its a new one
RemoteAddOp cmd = geogig.command(RemoteAddOp.class);
cmd.setName(ri.getName());
cmd.setURL(ri.getURL());
cmd.setUserName(ri.getUserName());
cmd.setPassword(ri.getPassword());
try {
cmd.call();
} catch (RuntimeException e) {
throw new RuntimeException(
"Error adding remote " + ri.getName() + ": " + e.getMessage(), e);
}
} else {
// handle upadtes
RemoteInfo old = currentRemotes.remove(ri.getId());
Preconditions.checkNotNull(old);
if (old.equals(ri)) {
continue;// didn't change
}
try {
geogig.command(RemoteRemoveOp.class).setName(old.getName()).call();
RemoteAddOp addop = geogig.command(RemoteAddOp.class);
addop.setName(ri.getName()).setURL(ri.getURL()).setUserName(ri.getUserName())
.setPassword(ri.getPassword());
addop.call();
} catch (RuntimeException e) {
throw new RuntimeException(
"Error updating remote " + ri.getName() + ": " + e.getMessage(), e);
}
}
}
}
private void updateConfig(Context geogig, Map<String, String> currentConfig, List<ConfigEntry> newConfig, boolean global) {
ConfigScope scope = global ? ConfigScope.GLOBAL : ConfigScope.LOCAL;
for (ConfigEntry entry : newConfig) {
if (currentConfig.containsKey(entry.getName())) {
if (!currentConfig.get(entry.getName()).equals(entry.getValue())) {
// entry changed
geogig.command(ConfigOp.class)//
.setAction(ConfigAction.CONFIG_SET)//
.setName(entry.getName())//
.setValue(entry.getValue())//
.setScope(scope)//
.call();
}
currentConfig.remove(entry.getName());
} else {
// new entry
geogig.command(ConfigOp.class)//
.setAction(ConfigAction.CONFIG_SET)//
.setName(entry.getName())//
.setValue(entry.getValue())//
.setScope(scope)//
.call();
}
}
if (!isNew) {
for(Entry<String, String> entry : currentConfig.entrySet()) {
// removed entry
geogig.command(ConfigOp.class)//
.setAction(ConfigAction.CONFIG_UNSET)//
.setName(entry.getKey())//
.setScope(scope)//
.call();
}
}
}
protected abstract void saved(RepositoryInfo info, AjaxRequestTarget target);
protected abstract void cancelled(AjaxRequestTarget target);
}