Skip to content

Commit

Permalink
Make sure geogig's ConfigStore works on a clustered environment
Browse files Browse the repository at this point in the history
Geogig plugin's ConfigStore saves serialized RepositoryInfo instances
to GeoServers ResourceStore. Initially developer against the
default file based ResourceStore, it assumed it could freely
cache the RepositoryInfo instances.

On a clustered environemnt, modifications might occur on different
cluster nodes. This patch makes it so ResourceStore events are
properly handled for create/changed/deleted notifications by keeping
ConfigStore (in charge of storing the serialized RepositoryInfo instances)
and RepositoryManager (in charge of actual geogig Repository instances)
in synch, even when events come from different servers.
  • Loading branch information
Gabriel Roldan committed Jun 23, 2017
1 parent 8d01089 commit be2ee5d
Show file tree
Hide file tree
Showing 15 changed files with 608 additions and 219 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ public Repository load(final String repoId) throws Exception {
public Repository get(final String repositoryId) throws IOException {
try {
return repoCache.get(repositoryId);
} catch (ExecutionException e) {
} catch (Throwable e) {
Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
throw new IOException("Error obtaining cached geogig instance for repo " + repositoryId,
e.getCause());
Throwable cause = e.getCause();
throw new IOException("Error obtaining cached geogig instance for repo " + repositoryId
+ ": " + cause.getMessage(), cause);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;

public class RepositoryInfo implements Serializable {
public class RepositoryInfo implements Serializable, Cloneable {

private static final long serialVersionUID = -5946705936987075713L;

Expand All @@ -36,6 +37,7 @@ public class RepositoryInfo implements Serializable {
private java.net.URI location;

private String maskedLocation;

/**
* Stores the "nice" name for a repo. This is the name that is shown in the Repository list, as
* well as what is stored in the GeoGIG repository config. It is transient, as we don't want to
Expand All @@ -44,6 +46,8 @@ public class RepositoryInfo implements Serializable {
*/
private transient String repoName;

private transient long lastModified;

public RepositoryInfo() {
this(null);
}
Expand All @@ -52,6 +56,14 @@ public RepositoryInfo() {
this.id = id;
}

public @Override RepositoryInfo clone() {
try {
return (RepositoryInfo) super.clone();
} catch (CloneNotSupportedException e) {
throw Throwables.propagate(e);
}
}

private Object readResolve() {
if (parentDirectory != null && name != null) {
File file = new File(new File(parentDirectory), name).getAbsoluteFile();
Expand Down Expand Up @@ -135,4 +147,12 @@ public String toString() {
return new StringBuilder("[id:").append(getId()).append(", URI:").append(getLocation())
.append("]").toString();
}

long getLastModified() {
return lastModified;
}

void setLastModified(long timestamp) {
this.lastModified = timestamp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import static org.geoserver.catalog.Predicates.equal;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
Expand Down Expand Up @@ -54,11 +53,11 @@
import org.locationtech.geogig.repository.impl.GlobalContextBuilder;
import org.opengis.filter.Filter;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;

Expand All @@ -70,35 +69,19 @@ public class RepositoryManager implements GeoServerInitializer {
}
}

private static class StaticSupplier implements Supplier<RepositoryManager>, Serializable {
private static final long serialVersionUID = 3706728433275296134L;

@Override
public RepositoryManager get() {
return RepositoryManager.get();
}
}
private static final String REPO_ROOT = "geogig/repos";

private final ConfigStore configStore;
private ConfigStore configStore;

private final ResourceStore resourceStore;
private ResourceStore resourceStore;

private final RepositoryCache repoCache;
private RepositoryCache repoCache;

private static RepositoryManager INSTANCE;

private Catalog catalog;

private static final String REPO_ROOT = "geogig/repos";

private static final RepositoryInfoChangedCallback REPO_CHANGED_CALLBACK = new RepositoryInfoChangedCallback() {
@Override
public void repositoryInfoChanged(String repoId) {
if (INSTANCE != null && INSTANCE.repoCache != null) {
INSTANCE.repoCache.invalidate(repoId);
}
}
};
private RepositoryInfoChangedCallback REPO_CHANGED_CALLBACK;

public static synchronized RepositoryManager get() {
if (INSTANCE == null) {
Expand All @@ -108,24 +91,35 @@ public static synchronized RepositoryManager get() {
return INSTANCE;
}

public static void close() {
public void dispose() {
configStore.removeRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK);
repoCache.invalidateAll();
}

public synchronized static void close() {
if (INSTANCE != null) {
INSTANCE.configStore.removeRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK);
INSTANCE.repoCache.invalidateAll();
INSTANCE.dispose();
INSTANCE = null;
}
}

public static Supplier<RepositoryManager> supplier() {
return new StaticSupplier();
public RepositoryManager(ConfigStore configStore, ResourceStore resourceStore) {
init(configStore, resourceStore);
}

public RepositoryManager(ConfigStore configStore, ResourceStore resourceStore) {
@VisibleForTesting
RepositoryManager() {

}

@VisibleForTesting
void init(ConfigStore configStore, ResourceStore resourceStore) {
checkNotNull(configStore);
checkNotNull(resourceStore);
this.configStore = configStore;
this.resourceStore = resourceStore;
this.repoCache = new RepositoryCache(this);
REPO_CHANGED_CALLBACK = (repoId) -> invalidate(repoId);
this.configStore.addRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK);
}

Expand Down Expand Up @@ -153,8 +147,10 @@ public Repository createRepo(final Hints hints) {
} else {
// no location set yet, generate one
// NOTE: If the resource store does not support a file system, the repository will be
// created in a temporary directory. If this is the case, remove any repository
// resolvers that can resolve a 'file' URI to prevent the creation of such repos.
// created
// in a temporary directory. If this is the case, remove any repository resolvers that
// can
// resolve a 'file' URI to prevent the creation of such repos.
Resource root = resourceStore.get(REPO_ROOT);
File repoDir = root.get(UUID.randomUUID().toString()).dir();
if (!repoDir.exists()) {
Expand All @@ -178,12 +174,8 @@ public Repository createRepo(final Hints hints) {
return repository;
}

public RepositoryInfo get(final String repoId) throws IOException {
try {
return configStore.get(repoId);
} catch (FileNotFoundException e) {
throw new NoSuchElementException("No repository with ID " + repoId + " exists");
}
public RepositoryInfo get(final String repoId) throws NoSuchElementException {
return configStore.get(repoId);
}

/**
Expand All @@ -193,11 +185,24 @@ public RepositoryInfo get(final String repoId) throws IOException {
*
* @return a RepositoryInfo object, if found. If not found, returns null.
*/
public RepositoryInfo getByRepoName(final String name) {
public @Nullable RepositoryInfo getByRepoName(final String name) {
RepositoryInfo info = configStore.getByName(name);
return info;
}

public @Nullable RepositoryInfo getByRepoLocation(final URI repoURI) {
RepositoryInfo info = configStore.getByLocation(repoURI);
return info;
}

public boolean repoExistsByName(final String name) {
return configStore.repoExistsByName(name);
}

public boolean repoExistsByLocation(URI location) {
return configStore.repoExistsByLocation(location);
}

public List<DataStoreInfo> findGeogigStores() {
return findGeogigStores(this.catalog);
}
Expand Down Expand Up @@ -231,7 +236,7 @@ public List<DataStoreInfo> findDataStores(final String repoId) {
String repoName = null;
try {
repoName = this.get(repoId).getRepoName();
} catch (IOException ioe) {
} catch (NoSuchElementException ioe) {
Throwables.propagate(ioe);
}
Filter filter = equal("type", GeoGigDataStoreFactory.DISPLAY_NAME);
Expand Down Expand Up @@ -309,13 +314,8 @@ public RepositoryInfo save(RepositoryInfo info) {
create(info);
} else {
// see if the name has changed. If so, update the repo config
try {
RepositoryInfo currentInfo = get(info.getId());
handleRepoRename(currentInfo, info);
} catch (IOException ioe) {

}

RepositoryInfo currentInfo = get(info.getId());
handleRepoRename(currentInfo, info);
}
// so far we don't need to invalidate the GeoGIG instance from the cache here... re-evaluate
// if any configuration option would require so in the future
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager;
Expand Down Expand Up @@ -72,7 +71,7 @@ public Optional<RepositoryInfo> findRepository(Request request) {
} else {
return Optional.absent();
}
} catch (NoSuchElementException | IOException e) {
} catch (RuntimeException e) {
return Optional.absent();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,11 @@ protected Representation runCommand(Variant variant, Request request, MediaType
// before importing the repository, extract the repository name from the request and see if
// a repository with that name already exists
final String repoName = RESTUtils.getStringAttribute(request, "repository");
if (repoName != null) {
if (RepositoryManager.get().getByRepoName(repoName) != null) {
// repo already exists
throw new CommandSpecException(
if (repoName != null && RepositoryManager.get().repoExistsByName(repoName)) {
// repo already exists
throw new CommandSpecException(
"The specified repository name is already in use, please try a different name",
Status.CLIENT_ERROR_CONFLICT);
}
}
// all good
return super.runCommand(variant, request, outputFormat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ protected Representation runCommand(Variant variant, Request request, MediaType
// before running the Init command, extract the repository name from the request and see if
// a repository with that name already exists
final String repoName = RESTUtils.getStringAttribute(request, "repository");
if (repoName != null) {
if (RepositoryManager.get().getByRepoName(repoName) != null) {
// repo already exists
throw new CommandSpecException(
"The specified repository name is already in use, please try a different name",
Status.CLIENT_ERROR_CONFLICT);
}
if (repoName != null && RepositoryManager.get().repoExistsByName(repoName)) {
// repo already exists
throw new CommandSpecException(
"The specified repository name is already in use, please try a different name",
Status.CLIENT_ERROR_CONFLICT);
}
Representation representation = super.runCommand(variant, request, outputFormat);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class BranchSelectionPanel extends FormComponentPanel<String> {

private final IModel<String> repositoryUriModel;

private Supplier<RepositoryManager> manager = RepositoryManager.supplier();
private Supplier<RepositoryManager> manager = () -> RepositoryManager.get();

public BranchSelectionPanel(String id, IModel<String> repositoryUriModel,
IModel<String> branchNameModel, Form<DataStoreInfo> storeEditForm) {
Expand Down Expand Up @@ -103,7 +103,7 @@ public void updateChoices(boolean reportError, Form<?> form) {
if (reportError) {
form.error("Could not list branches: " + e.getMessage());
}
}
}
String current = (String) choice.getModelObject();
if (current != null && !branchNames.contains(current)) {
branchNames.add(0, current);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public void validate(IValidatable<RepositoryInfo> validatable) {
final URI location = repo.getLocation();
final RepositoryResolver resolver = RepositoryResolver.lookup(location);
final String scheme = location.getScheme();
if (isNew && repoNameExists(repo.getRepoName())) {
final boolean nameExists = RepositoryManager.get()
.repoExistsByName(repo.getRepoName());
if (isNew && nameExists) {
error.addKey("errRepositoryNameExists");
} else if ("file".equals(scheme)) {
File repoDir = new File(location);
Expand Down Expand Up @@ -98,8 +100,4 @@ public void convertInput() {
RepositoryInfo modelObject = getModelObject();
setConvertedInput(modelObject);
}

private boolean repoNameExists(String repoName) {
return RepositoryManager.get().getByRepoName(repoName) != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,16 @@ public void validate(IValidatable<RepositoryInfo> validatable) {
ValidationError error = new ValidationError();
RepositoryInfo repo = validatable.getValue();
// block duplicate names first
if (null != RepositoryManager.get().getByRepoName(repo.getRepoName())) {
if (RepositoryManager.get().repoExistsByName(repo.getRepoName())) {
error.addKey("errRepositoryNameExists");
validatable.error(error);
return;
}
final URI location = repo.getLocation();
// look for already configured repos
List<RepositoryInfo> existingRepos = RepositoryManager.get().getAll();
for (RepositoryInfo configuredRepo : existingRepos) {
if (configuredRepo.getLocation().equals(location)) {
error.addKey("errRepositoryAlreadyConfigured");
// quit looking
break;
}
if (RepositoryManager.get().repoExistsByLocation(location)) {
error.addKey("errRepositoryAlreadyConfigured");
return;
}
if (error.getKeys().isEmpty()) {
final RepositoryResolver resolver = RepositoryResolver.lookup(location);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@
*/
package org.geogig.geoserver.web.repository;

import java.io.IOException;

import org.apache.wicket.model.LoadableDetachableModel;
import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager;

import com.google.common.base.Throwables;

/**
* A RepositoryInfo detachable model that holds the store id to retrieve it on demand from the
* catalog
Expand All @@ -29,10 +25,6 @@ public RepositoryInfoDetachableModel(RepositoryInfo repoInfo) {

@Override
protected RepositoryInfo load() {
try {
return RepositoryManager.get().get(id);
} catch (IOException e) {
throw Throwables.propagate(e);
}
return RepositoryManager.get().get(id);
}
}

0 comments on commit be2ee5d

Please sign in to comment.