/* Copyright (c) 2012-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.repository;
import java.io.Closeable;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Platform;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.plumbing.FindTreeChild;
import org.locationtech.geogig.api.plumbing.RefParse;
import org.locationtech.geogig.api.plumbing.ResolveGeogigDir;
import org.locationtech.geogig.api.plumbing.ResolveTreeish;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.RevParse;
import org.locationtech.geogig.api.porcelain.ConfigOp;
import org.locationtech.geogig.api.porcelain.ConfigOp.ConfigAction;
import org.locationtech.geogig.di.PluginDefaults;
import org.locationtech.geogig.di.Singleton;
import org.locationtech.geogig.storage.ConfigDatabase;
import org.locationtech.geogig.storage.DeduplicationService;
import org.locationtech.geogig.storage.GraphDatabase;
import org.locationtech.geogig.storage.ObjectDatabase;
import org.locationtech.geogig.storage.ObjectInserter;
import org.locationtech.geogig.storage.RefDatabase;
import org.locationtech.geogig.storage.StagingDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* A repository is a collection of commits, each of which is an archive of what the project's
* working tree looked like at a past date, whether on your machine or someone else's.
* <p>
* It also defines HEAD (see below), which identifies the branch or commit the current working tree
* stemmed from. Lastly, it contains a set of branches and tags, to identify certain commits by
* name.
* </p>
*
* @see WorkingTree
*/
@Singleton
public class Repository implements Context {
private static Logger LOGGER = LoggerFactory.getLogger(Repository.class);
public static interface RepositoryListener {
public void opened(Repository repo);
public void closed();
}
private List<RepositoryListener> listeners = Lists.newCopyOnWriteArrayList();
private Context injector;
private URL repositoryLocation;
public static final String DEPTH_CONFIG_KEY = "core.depth";
private ExecutorService executor;
@Inject
public Repository(Context injector, ExecutorService executor) {
this.injector = injector;
this.executor = executor;
}
public void addListener(RepositoryListener listener) {
if (!this.listeners.contains(listener)) {
this.listeners.add(listener);
}
}
public void configure() throws RepositoryConnectionException {
injector.refDatabase().configure();
injector.objectDatabase().configure();
injector.graphDatabase().configure();
injector.stagingDatabase().configure();
}
public void open() throws RepositoryConnectionException {
injector.refDatabase().checkConfig();
injector.objectDatabase().checkConfig();
injector.graphDatabase().checkConfig();
injector.stagingDatabase().checkConfig();
injector.refDatabase().create();
injector.objectDatabase().open();
injector.graphDatabase().open();
injector.stagingDatabase().open();
Optional<URL> repoUrl = command(ResolveGeogigDir.class).call();
Preconditions.checkState(repoUrl.isPresent(), "Repository URL can't be located");
this.repositoryLocation = repoUrl.get();
for (RepositoryListener l : listeners) {
l.opened(this);
}
}
/**
* Closes the repository.
*/
public synchronized void close() {
close(injector.refDatabase());
close(injector.objectDatabase());
close(injector.graphDatabase());
close(injector.stagingDatabase());
for (RepositoryListener l : listeners) {
l.closed();
}
executor.shutdownNow();
}
private void close(Closeable db) {
try {
db.close();
} catch (Exception e) {
LOGGER.error("Error closing database " + db, e);
}
}
public URL getLocation() {
return repositoryLocation;
}
/**
* Finds and returns an instance of a command of the specified class.
*
* @param commandClass the kind of command to locate and instantiate
* @return a new instance of the requested command class, with its dependencies resolved
*/
public <T extends AbstractGeoGigOp<?>> T command(Class<T> commandClass) {
return injector.command(commandClass);
}
/**
* Test if a blob exists in the object database
*
* @param id the ID of the blob in the object database
* @return true if the blob exists with the parameter ID, false otherwise
*/
public boolean blobExists(final ObjectId id) {
return objectDatabase().exists(id);
}
/**
* @param revStr the string to parse
* @return the parsed {@link Ref}, or {@link Optional#absent()} if it did not parse.
*/
public Optional<Ref> getRef(final String revStr) {
Optional<Ref> ref = command(RefParse.class).setName(revStr).call();
return ref;
}
/**
* @return the {@link Ref} pointed to by HEAD, or {@link Optional#absent()} if it could not be
* resolved.
*/
public Optional<Ref> getHead() {
return getRef(Ref.HEAD);
}
/**
* Determines if a commit with the given {@link ObjectId} exists in the object database.
*
* @param id the id to look for
* @return true if the object was found, false otherwise
*/
public boolean commitExists(final ObjectId id) {
try {
RevObject revObject = objectDatabase().get(id);
return revObject instanceof RevCommit;
} catch (IllegalArgumentException e) {
return false;
}
}
/**
* Gets the {@link RevCommit} with the given {@link ObjectId} from the object database.
*
* @param commitId the {@code ObjectId} for the commit
* @return the {@code RevCommit}
*/
public RevCommit getCommit(final ObjectId commitId) {
RevCommit commit = objectDatabase().getCommit(commitId);
return commit;
}
/**
* Test if a tree exists in the object database
*
* @param id the ID of the tree in the object database
* @return true if the tree exists with the parameter ID, false otherwise
*/
public boolean treeExists(final ObjectId id) {
try {
objectDatabase().getTree(id);
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
/**
* @return the {@link ObjectId} of the root tree
*/
public ObjectId getRootTreeId() {
// find the root tree
ObjectId commitId = command(RevParse.class).setRefSpec(Ref.HEAD).call().get();
if (commitId.isNull()) {
return commitId;
}
RevCommit commit = command(RevObjectParse.class).setRefSpec(commitId.toString())
.call(RevCommit.class).get();
ObjectId treeId = commit.getTreeId();
return treeId;
}
/**
* @return an {@link ObjectInserter} to insert objects into the object database
*/
public ObjectInserter newObjectInserter() {
return objectDatabase().newObjectInserter();
}
/**
* @param contentId the {@link ObjectId} of the feature to get
* @return the {@link RevFeature} that was found in the object database
*/
public RevFeature getFeature(final ObjectId contentId) {
RevFeature revFeature = objectDatabase().getFeature(contentId);
return revFeature;
}
/**
* @return the existing {@link RevTree} pointed to by HEAD, or a new {@code RevTree} if it did
* not exist
*/
public RevTree getOrCreateHeadTree() {
Optional<ObjectId> headTreeId = command(ResolveTreeish.class).setTreeish(Ref.HEAD).call();
if (!headTreeId.isPresent()) {
return RevTree.EMPTY;
}
return getTree(headTreeId.get());
}
/**
* @param treeId the tree to retrieve
* @return the {@link RevTree} referred to by the given {@link ObjectId}
*/
public RevTree getTree(ObjectId treeId) {
return command(RevObjectParse.class).setObjectId(treeId).call(RevTree.class).get();
}
/**
* @param path the path to search for
* @return an {@link Optional} of the {@link Node} for the child, or {@link Optional#absent()}
* if it wasn't found
*/
public Optional<Node> getRootTreeChild(String path) {
Optional<NodeRef> nodeRef = command(FindTreeChild.class).setChildPath(path).call();
if (nodeRef.isPresent()) {
return Optional.of(nodeRef.get().getNode());
} else {
return Optional.absent();
}
}
/**
* Search the given tree for the child path.
*
* @param tree the tree to search
* @param childPath the path to search for
* @return an {@link Optional} of the {@link Node} for the child path, or
* {@link Optional#absent()} if it wasn't found
*/
public Optional<Node> getTreeChild(RevTree tree, String childPath) {
Optional<NodeRef> nodeRef = command(FindTreeChild.class).setParent(tree)
.setChildPath(childPath).call();
if (nodeRef.isPresent()) {
return Optional.of(nodeRef.get().getNode());
} else {
return Optional.absent();
}
}
/**
* Gets the depth of the repository, or {@link Optional#absent} if this is not a shallow clone.
*
* @return the depth
*/
public Optional<Integer> getDepth() {
int repoDepth = 0;
Optional<Map<String, String>> depthResult = command(ConfigOp.class)
.setAction(ConfigAction.CONFIG_GET).setName(DEPTH_CONFIG_KEY).call();
if (depthResult.isPresent()) {
String depthString = depthResult.get().get(DEPTH_CONFIG_KEY);
if (depthString != null) {
repoDepth = Integer.parseInt(depthString);
}
}
if (repoDepth == 0) {
return Optional.absent();
}
return Optional.of(repoDepth);
}
/**
* @return true if this is a sparse (mapped) clone.
*/
public boolean isSparse() {
Optional<Map<String, String>> sparseResult = command(ConfigOp.class)
.setAction(ConfigAction.CONFIG_GET).setName("sparse.filter").call();
return sparseResult.isPresent();
}
@Override
public WorkingTree workingTree() {
return injector.workingTree();
}
@Override
public StagingArea index() {
return injector.index();
}
@Override
public RefDatabase refDatabase() {
return injector.refDatabase();
}
@Override
public Platform platform() {
return injector.platform();
}
@Override
public ObjectDatabase objectDatabase() {
return injector.objectDatabase();
}
@Override
public StagingDatabase stagingDatabase() {
return injector.stagingDatabase();
}
@Override
public ConfigDatabase configDatabase() {
return injector.configDatabase();
}
@Override
public GraphDatabase graphDatabase() {
return injector.graphDatabase();
}
@Deprecated
@Override
public Repository repository() {
return this;
}
@Override
public DeduplicationService deduplicationService() {
return injector.deduplicationService();
}
@Override
public PluginDefaults pluginDefaults() {
return injector.pluginDefaults();
}
}