Add JavaDoc, minor refactor

This commit is contained in:
Winston Li
2016-10-09 21:13:11 +01:00
parent c23c11973a
commit 25a988daa3
83 changed files with 1449 additions and 480 deletions

View File

@@ -1,11 +1,30 @@
package uk.ac.ic.wlgitbridge;
import uk.ac.ic.wlgitbridge.application.GitBridgeApp;
import uk.ac.ic.wlgitbridge.bridge.Bridge;
import uk.ac.ic.wlgitbridge.server.GitBridgeServer;
import uk.ac.ic.wlgitbridge.util.Log;
/**
* Created by Winston on 01/11/14.
*/
/**
* This is the entry point into the Git Bridge.
*
* It is responsible for creating the {@link GitBridgeApp} and then running it.
*
* The {@link GitBridgeApp} parses args and creates the {@link GitBridgeServer}.
*
* The {@link GitBridgeServer} creates the {@link Bridge}, among other things.
*
* The {@link Bridge} is the heart of the Git Bridge. Start there, and follow
* the links outwards (which lead back to the Git users and the postback from
* the snapshot API) and inwards (which lead into the components of the Git
* Bridge: the configurable repo store, db store, and swap store, along with
* the project lock, the swap job, the snapshot API, the resource cache
* and the postback manager).
*/
public class Main {
public static void main(String[] args) {

View File

@@ -3,7 +3,6 @@ package uk.ac.ic.wlgitbridge.application;
import uk.ac.ic.wlgitbridge.application.config.Config;
import uk.ac.ic.wlgitbridge.application.exception.ArgsException;
import uk.ac.ic.wlgitbridge.application.exception.ConfigFileException;
import uk.ac.ic.wlgitbridge.git.exception.InvalidRootDirectoryPathException;
import uk.ac.ic.wlgitbridge.server.GitBridgeServer;
import uk.ac.ic.wlgitbridge.util.Log;
@@ -58,11 +57,6 @@ public class GitBridgeApp implements Runnable {
"Servlet exception when instantiating GitBridgeServer",
e
);
} catch (InvalidRootDirectoryPathException e) {
Log.error(
"Invalid root git directory path. Check your config file."
);
System.exit(EXIT_CODE_FAILED);
}
}

View File

@@ -49,8 +49,10 @@ public class Config implements JSONSource {
@Nullable
private SwapJobConfig swapJob;
public Config(String configFilePath) throws ConfigFileException,
IOException {
public Config(
String configFilePath
) throws ConfigFileException,
IOException {
this(new FileReader(configFilePath));
}

View File

@@ -9,7 +9,11 @@ public class Oauth2 {
private final String oauth2ClientSecret;
private final String oauth2Server;
public Oauth2(String oauth2ClientID, String oauth2ClientSecret, String oauth2Server) {
public Oauth2(
String oauth2ClientID,
String oauth2ClientSecret,
String oauth2Server
) {
this.oauth2ClientID = oauth2ClientID;
this.oauth2ClientSecret = oauth2ClientSecret;
this.oauth2Server = oauth2Server;

View File

@@ -3,5 +3,4 @@ package uk.ac.ic.wlgitbridge.application.exception;
/**
* Created by Winston on 03/11/14.
*/
public class ArgsException extends Exception {
}
public class ArgsException extends Exception {}

View File

@@ -4,8 +4,11 @@ import com.google.api.client.auth.oauth2.Credential;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import uk.ac.ic.wlgitbridge.bridge.db.DBStore;
import uk.ac.ic.wlgitbridge.bridge.db.ProjectState;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SqliteDBStore;
import uk.ac.ic.wlgitbridge.bridge.lock.LockGuard;
import uk.ac.ic.wlgitbridge.bridge.lock.ProjectLock;
import uk.ac.ic.wlgitbridge.bridge.repo.FSGitRepoStore;
import uk.ac.ic.wlgitbridge.bridge.repo.GitProjectRepo;
import uk.ac.ic.wlgitbridge.bridge.repo.ProjectRepo;
import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore;
import uk.ac.ic.wlgitbridge.bridge.resource.ResourceCache;
@@ -14,6 +17,8 @@ import uk.ac.ic.wlgitbridge.bridge.snapshot.NetSnapshotAPI;
import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotAPI;
import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJob;
import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJobConfig;
import uk.ac.ic.wlgitbridge.bridge.swap.job.SwapJobImpl;
import uk.ac.ic.wlgitbridge.bridge.swap.store.S3SwapStore;
import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore;
import uk.ac.ic.wlgitbridge.data.CandidateSnapshot;
import uk.ac.ic.wlgitbridge.data.ProjectLockImpl;
@@ -22,11 +27,20 @@ import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory;
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
import uk.ac.ic.wlgitbridge.data.model.Snapshot;
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
import uk.ac.ic.wlgitbridge.git.handler.WLReceivePackFactory;
import uk.ac.ic.wlgitbridge.git.handler.WLRepositoryResolver;
import uk.ac.ic.wlgitbridge.git.handler.WLUploadPackFactory;
import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook;
import uk.ac.ic.wlgitbridge.server.FileHandler;
import uk.ac.ic.wlgitbridge.server.PostbackContents;
import uk.ac.ic.wlgitbridge.server.PostbackHandler;
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest;
import uk.ac.ic.wlgitbridge.snapshot.getdoc.exception.InvalidProjectException;
import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotAttachment;
import uk.ac.ic.wlgitbridge.snapshot.push.PostbackManager;
import uk.ac.ic.wlgitbridge.snapshot.push.PostbackPromise;
import uk.ac.ic.wlgitbridge.snapshot.push.PushRequest;
import uk.ac.ic.wlgitbridge.snapshot.push.PushResult;
import uk.ac.ic.wlgitbridge.snapshot.push.exception.*;
@@ -41,6 +55,91 @@ import java.util.*;
/**
* Created by Winston on 16/11/14.
*/
/**
* This is the heart of the Git Bridge. You plug in all the parts (project
* lock, repo store, db store, swap store, snapshot api, resource cache and
* postback manager) is called by Git user requests and Overleaf postback
* requests.
*
* Follow these links to go "outward" (to input from Git users and Overleaf):
*
* 1. JGit hooks, which handle user Git requests:
*
* @see WLRepositoryResolver - used on all requests associate a repo with a
* project name, or fail
*
* @see WLUploadPackFactory - used to handle clones and fetches
*
* @see WLReceivePackFactory - used to handle pushes by setting a hook
* @see WriteLatexPutHook - the hook used to handle pushes
*
* 2. The Postback Servlet, which handles postbacks from the Overleaf app
* to confirm that a project is pushed. If a postback is lost, it's fine, we
* just update ourselves on the next access.
*
* @see PostbackHandler - the entry point for postbacks
*
* Follow these links to go "inward" (to the Git Bridge components):
*
* 1. The Project Lock, used to synchronise accesses to projects and shutdown
* the Git Bridge gracefully by preventing further lock acquiring.
*
* @see ProjectLock - the interface used for the Project Lock
* @see ProjectLockImpl - the default concrete implementation
*
* 2. The Repo Store, used to provide repository objects.
*
* The default implementation uses Git on the file system.
*
* @see RepoStore - the interface for the Repo Store
* @see FSGitRepoStore - the default concrete implementation
* @see ProjectRepo - an interface for an actual repo instance
* @see GitProjectRepo - the default concrete implementation
*
* 3. The DB Store, used to store persistent data such as the latest version
* of each project that we have (used for querying the Snapshot API), along
* with caching remote blobs.
*
* The default implementation is SQLite based.
*
* @see DBStore - the interface for the DB store
* @see SqliteDBStore - the default concrete implementation
*
* 4. The Swap Store, used to swap projects to when the disk goes over a
* certain data usage.
*
* The default implementation tarbzips projects to/from Amazon S3.
*
* @see SwapStore - the interface for the Swap Store
* @see S3SwapStore - the default concrete implementation
*
* 5. The Swap Job, which performs the actual swapping on the swap store based
* on various configuration options.
*
* @see SwapJob - the interface for the Swap Job
* @see SwapJobImpl - the default concrete implementation
*
* 6. The Snapshot API, which provides data from the Overleaf app.
*
* @see SnapshotAPI - the interface for the Snapshot API.
* @see NetSnapshotAPI - the default concrete implementation
*
* 7. The Resource Cache, which provides the data for attachment resources from
* URLs. It will generally fetch from the source on a cache miss.
*
* The default implementation uses the DB Store to maintain a mapping from
* URLs to files in an actual repo.
*
* @see ResourceCache - the interface for the Resource Cache
* @see UrlResourceCache - the default concrete implementation
*
* 8. The Postback Manager, which keeps track of pending postbacks. It stores a
* mapping from project names to postback promises.
*
* @see PostbackManager - the class
* @see PostbackPromise - the object waited on for a postback.
*
*/
public class Bridge {
private final ProjectLock lock;
@@ -53,9 +152,19 @@ public class Bridge {
private final SnapshotAPI snapshotAPI;
private final ResourceCache resourceCache;
private final PostbackManager postbackManager;
/**
* Creates a Bridge from its configurable parts, which are the repo, db and
* swap store, and the swap job config.
*
* This should be the method used to create a Bridge.
* @param repoStore The repo store to use
* @param dbStore The db store to use
* @param swapStore The swap store to use
* @param swapJobConfig The swap config to use, or empty for no-op
* @return The constructed Bridge.
*/
public static Bridge make(
RepoStore repoStore,
DBStore dbStore,
@@ -82,6 +191,18 @@ public class Bridge {
);
}
/**
* Creates a bridge from all of its components, not just its configurable
* parts. This is for substituting mock/stub components for testing.
* It's also used by Bridge.make to actually construct the bridge.
* @param lock the {@link ProjectLock} to use
* @param repoStore the {@link RepoStore} to use
* @param dbStore the {@link DBStore} to use
* @param swapStore the {@link SwapStore} to use
* @param swapJob the {@link SwapJob} to use
* @param snapshotAPI the {@link SnapshotAPI} to use
* @param resourceCache the {@link ResourceCache} to use
*/
Bridge(
ProjectLock lock,
RepoStore repoStore,
@@ -103,6 +224,15 @@ public class Bridge {
repoStore.purgeNonexistentProjects(dbStore.getProjectNames());
}
/**
* This performs the graceful shutdown of the Bridge, which is called by the
* shutdown hook. It acquires the project write lock, which prevents
* work being done for new projects (which acquire the read lock).
* Once it has the write lock, there are no readers left, so the git bridge
* can shut down gracefully.
*
* It is also used by the tests.
*/
void doShutdown() {
Log.info("Shutdown received.");
Log.info("Stopping SwapJob");
@@ -112,10 +242,18 @@ public class Bridge {
Log.info("Bye");
}
/**
* Starts the swap job, which will begin checking whether projects should be
* swapped with a configurable frequency.
*/
public void startSwapJob() {
swapJob.start();
}
/**
* Performs a check of inconsistencies in the DB. This was used to upgrade
* the schema.
*/
public void checkDB() {
Log.info("Checking DB");
File rootDir = repoStore.getRootDirectory();
@@ -143,6 +281,19 @@ public class Bridge {
}
}
/**
* Checks if a project exists by asking the snapshot API.
*
* The snapshot API is the source of truth because we can't know by
* ourselves whether a project exists. If a user creates a project on the
* app, and clones, the project is not on the git bridge disk and must ask
* the snapshot API whether it exists.
* @param oauth2 The oauth2 to use for the snapshot API
* @param projectName The project name
* @return true iff the project exists
* @throws ServiceMayNotContinueException if the connection fails
* @throws GitUserException if the user is not allowed access
*/
public boolean projectExists(
Credential oauth2,
String projectName
@@ -161,6 +312,16 @@ public class Bridge {
}
}
/**
* Synchronises the given repository with Overleaf.
*
* It acquires the project lock and calls
* {@link #updateRepositoryCritical(Credential, ProjectRepo)}
* @param oauth2 The oauth2 to use
* @param repo the repository to use
* @throws IOException
* @throws GitUserException
*/
public void updateRepository(
Credential oauth2,
ProjectRepo repo
@@ -172,6 +333,24 @@ public class Bridge {
}
}
/**
* Synchronises the given repository with Overleaf. The project lock must
* be acquired.
*
* If the project has never been cloned, it is git init'd. If the project
* is in swap, it is restored to disk. Otherwise, the project was already
* present.
*
* With the project present, snapshots are downloaded from the snapshot
* API with {@link #updateProject(Credential, ProjectRepo)}.
*
* Then, the last accessed time of the project is set to the current time.
* This is to support the LRU of the swap store.
* @param oauth2
* @param repo
* @throws IOException
* @throws GitUserException
*/
private void updateRepositoryCritical(
Credential oauth2,
ProjectRepo repo
@@ -195,7 +374,25 @@ public class Bridge {
);
}
public void putDirectoryContentsToProjectWithName(
/**
* The public call to push a project.
*
* It acquires the lock and calls {@link #pushCritical(
* Credential,
* String,
* RawDirectory,
* RawDirectory
* )}, catching exceptions, logging, and rethrowing them.
* @param oauth2 The oauth2 to use for the snapshot API
* @param projectName The name of the project to push to
* @param directoryContents The new contents of the project
* @param oldDirectoryContents The old contents of the project
* @param hostname
* @throws SnapshotPostException
* @throws IOException
* @throws ForbiddenException
*/
public void push(
Credential oauth2,
String projectName,
RawDirectory directoryContents,
@@ -203,7 +400,7 @@ public class Bridge {
String hostname
) throws SnapshotPostException, IOException, ForbiddenException {
try (LockGuard __ = lock.lockGuard(projectName)) {
pushToProjectCritical(
pushCritical(
oauth2,
projectName,
directoryContents,
@@ -226,7 +423,51 @@ public class Bridge {
}
}
private void pushToProjectCritical(
/**
* Does the work of pushing to a project, assuming the project lock is held.
* The {@link WriteLatexPutHook} is the original caller, and when we return
* without throwing, the commit is committed.
*
* We start off by creating a postback key, which is given in the url when
* the Overleaf app tries to access the atts.
*
* Then creates a {@link CandidateSnapshot} from the old and new project
* contents. The
* {@link CandidateSnapshot} is created using
* {@link #createCandidateSnapshot(String, RawDirectory, RawDirectory)},
* which creates the snapshot object and writes the push files to the
* atts directory, which is served by the {@link PostbackHandler}.
* The files are deleted at the end of a try-with-resources block.
*
* Then 3 things are used to make the push request to the snapshot API:
* 1. The oauth2
* 2. The candidate snapshot
* 3. The postback key
*
* If the snapshot API reports this as not successful, we immediately throw
* an {@link OutOfDateException}, which goes back to the user.
*
* Otherwise, we wait (with a timeout) on a promise from the postback
* manager, which can throw back to the user.
*
* If this is successful, we approve the snapshot with
* {@link #approveSnapshot(int, CandidateSnapshot)}, which updates our side
* of the push: the latest version and the URL index store.
*
* Then, we set the last accessed time for the swap store.
*
* Finally, after we return, the push to the repo from the hook is
* successful and the repo gets updated.
*
* @param oauth2
* @param projectName
* @param directoryContents
* @param oldDirectoryContents
* @throws IOException
* @throws ForbiddenException
* @throws SnapshotPostException
*/
private void pushCritical(
Credential oauth2,
String projectName,
RawDirectory directoryContents,
@@ -291,16 +532,45 @@ public class Bridge {
}
}
/**
* A public call that should originate from the {@link FileHandler}.
*
* The {@link FileHandler} serves atts to the Overleaf app during a push.
* The Overleaf app includes the postback key in the request, which was
* originally given on a push request.
*
* This method checks that the postback key matches, and throws if not.
*
* The FileHandler should not serve the file if this throws.
* @param projectName The project name that this key belongs to
* @param postbackKey The key
* @throws InvalidPostbackKeyException If the key doesn't match
*/
public void checkPostbackKey(String projectName, String postbackKey)
throws InvalidPostbackKeyException {
postbackManager.checkPostbackKey(projectName, postbackKey);
}
/* Called by postback thread. */
public void postbackReceivedSuccessfully(String projectName,
String postbackKey,
int versionID)
throws UnexpectedPostbackException {
/**
* A public call that originates from the postback thread
* {@link PostbackContents#processPostback()}, i.e. once the Overleaf app
* has fetched all the atts and has committed the push and is happy, it
* calls back here, fulfilling the promise that the push
* {@link #push(Credential, String, RawDirectory, RawDirectory, String)}
* is waiting on.
*
* The Overleaf app will have invented a new version for the push, which is
* passed to the promise for the original push request to update the app.
* @param projectName The name of the project being pushed to
* @param postbackKey The postback key being used
* @param versionID the new version id to use
* @throws UnexpectedPostbackException if the postback key is invalid
*/
public void postbackReceivedSuccessfully(
String projectName,
String postbackKey,
int versionID
) throws UnexpectedPostbackException {
Log.info(
"[{}]" +
" Postback received by postback thread, version: {}",
@@ -313,10 +583,23 @@ public class Bridge {
);
}
public void postbackReceivedWithException(String projectName,
String postbackKey,
SnapshotPostException exception)
throws UnexpectedPostbackException {
/**
* As with {@link #postbackReceivedSuccessfully(String, String, int)},
* but with an exception instead.
*
* This is based on the JSON body of the postback from the Overleaf app.
*
* The most likely problem is an {@link OutOfDateException}.
* @param projectName The name of the project
* @param postbackKey The postback key being used
* @param exception The exception encountered
* @throws UnexpectedPostbackException If the postback key is invalid
*/
public void postbackReceivedWithException(
String projectName,
String postbackKey,
SnapshotPostException exception
) throws UnexpectedPostbackException {
Log.warn("[{}] Postback received with exception", projectName);
postbackManager.postExceptionForProject(
projectName,
@@ -327,6 +610,19 @@ public class Bridge {
/* PRIVATE */
/**
* Called by {@link #updateRepositoryCritical(Credential, ProjectRepo)}.
*
* Does the actual work of getting the snapshots for a project from the
* snapshot API and committing them to a repo.
*
* If any snapshots were found, sets the latest version for the project.
*
* @param oauth2
* @param repo
* @throws IOException
* @throws GitUserException
*/
private void updateProject(
Credential oauth2,
ProjectRepo repo
@@ -349,9 +645,23 @@ public class Bridge {
}
}
private void makeCommitsFromSnapshots(ProjectRepo repo,
Collection<Snapshot> snapshots)
throws IOException, GitUserException {
/**
* Called by {@link #updateProject(Credential, ProjectRepo)}.
*
* Performs the actual Git commits on the disk.
*
* Each commit adds files to the db store
* ({@link ResourceCache#get(String, String, String, Map, Map)},
* and then removes any files that were deleted.
* @param repo The repository to commit to
* @param snapshots The snapshots to commit
* @throws IOException If an IOException occurred
* @throws SizeLimitExceededException If one of the files was too big.
*/
private void makeCommitsFromSnapshots(
ProjectRepo repo,
Collection<Snapshot> snapshots
) throws IOException, SizeLimitExceededException {
String name = repo.getProjectName();
for (Snapshot snapshot : snapshots) {
Map<String, RawFile> fileTable = repo.getFiles();
@@ -389,6 +699,20 @@ public class Bridge {
}
}
/**
* Called by
* {@link #pushCritical(Credential, String, RawDirectory, RawDirectory)}.
*
* This call consists of 2 things: Creating the candidate snapshot,
* and writing the atts to the atts directory.
*
* The candidate snapshot RAIIs away those atts (use try-with-resources).
* @param projectName The name of the project
* @param directoryContents The new directory contents
* @param oldDirectoryContents The old directory contents
* @return The {@link CandidateSnapshot} created
* @throws IOException If an I/O exception occurred on writing
*/
private CandidateSnapshot createCandidateSnapshot(
String projectName,
RawDirectory directoryContents,
@@ -404,6 +728,16 @@ public class Bridge {
return candidateSnapshot;
}
/**
* Called by
* {@link #pushCritical(Credential, String, RawDirectory, RawDirectory)}.
*
* This method approves a push by setting the latest version and removing
* any deleted files from the db store (files were already added by the
* resources cache).
* @param versionID
* @param candidateSnapshot
*/
private void approveSnapshot(
int versionID,
CandidateSnapshot candidateSnapshot
@@ -419,6 +753,4 @@ public class Bridge {
);
}
}

View File

@@ -5,15 +5,10 @@ import uk.ac.ic.wlgitbridge.bridge.db.DBInitException;
import uk.ac.ic.wlgitbridge.bridge.db.DBStore;
import uk.ac.ic.wlgitbridge.bridge.db.ProjectState;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.query.*;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter.ProjectsAddLastAccessed;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create.CreateIndexURLIndexStore;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create.CreateProjectsIndexLastAccessed;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create.CreateProjectsTableSQLUpdate;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create.CreateURLIndexStoreSQLUpdate;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.delete.DeleteFilesForProjectSQLUpdate;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert.AddURLIndexSQLUpdate;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert.SetProjectLastAccessedTime;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert.SetProjectSQLUpdate;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.alter.*;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.create.*;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.delete.*;
import uk.ac.ic.wlgitbridge.bridge.db.sqlite.update.insert.*;
import java.io.File;
import java.sql.*;

View File

@@ -35,7 +35,9 @@ public class GetLatestVersionForProjectSQLQuery implements SQLQuery<Integer> {
}
@Override
public void addParametersToStatement(PreparedStatement statement) throws SQLException {
public void addParametersToStatement(
PreparedStatement statement
) throws SQLException {
statement.setString(1, projectName);
}
}

View File

@@ -12,7 +12,10 @@ import java.sql.SQLException;
public class GetPathForURLInProjectSQLQuery implements SQLQuery<String> {
private static final String GET_URL_INDEXES_FOR_PROJECT_NAME =
"SELECT `path` FROM `url_index_store` WHERE `project_name` = ? AND `url` = ?";
"SELECT `path` "
+ "FROM `url_index_store` "
+ "WHERE `project_name` = ? "
+ "AND `url` = ?";
private final String projectName;
private final String url;
@@ -37,7 +40,9 @@ public class GetPathForURLInProjectSQLQuery implements SQLQuery<String> {
}
@Override
public void addParametersToStatement(PreparedStatement statement) throws SQLException {
public void addParametersToStatement(
PreparedStatement statement
) throws SQLException {
statement.setString(1, projectName);
statement.setString(2, url);
}

View File

@@ -4,7 +4,7 @@ import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SQLQuery;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
/**
@@ -16,8 +16,10 @@ public class GetProjectNamesSQLQuery implements SQLQuery<List<String>> {
"SELECT `name` FROM `projects`";
@Override
public List<String> processResultSet(ResultSet resultSet) throws SQLException {
List<String> projectNames = new LinkedList<String>();
public List<String> processResultSet(
ResultSet resultSet
) throws SQLException {
List<String> projectNames = new ArrayList<>();
while (resultSet.next()) {
projectNames.add(resultSet.getString("name"));
}

View File

@@ -29,7 +29,9 @@ public class GetProjectState implements SQLQuery<ProjectState> {
}
@Override
public ProjectState processResultSet(ResultSet resultSet) throws SQLException {
public ProjectState processResultSet(
ResultSet resultSet
) throws SQLException {
while (resultSet.next()) {
if (resultSet.getTimestamp("last_accessed") == null) {
return ProjectState.SWAPPED;
@@ -40,7 +42,9 @@ public class GetProjectState implements SQLQuery<ProjectState> {
}
@Override
public void addParametersToStatement(PreparedStatement statement) throws SQLException {
public void addParametersToStatement(
PreparedStatement statement
) throws SQLException {
statement.setString(1, projectName);
}

View File

@@ -17,7 +17,10 @@ public class DeleteFilesForProjectSQLUpdate implements SQLUpdate {
private final String projectName;
private final String[] paths;
public DeleteFilesForProjectSQLUpdate(String projectName, String... paths) {
public DeleteFilesForProjectSQLUpdate(
String projectName,
String... paths
) {
this.projectName = projectName;
this.paths = paths;
}

View File

@@ -11,8 +11,9 @@ import java.sql.SQLException;
public class SetProjectSQLUpdate implements SQLUpdate {
private static final String SET_PROJECT =
"INSERT OR REPLACE INTO `projects`(`name`, `version_id`, `last_accessed`) " +
"VALUES (?, ?, DATETIME('now'));\n";
"INSERT OR REPLACE "
+ "INTO `projects`(`name`, `version_id`, `last_accessed`) "
+ "VALUES (?, ?, DATETIME('now'));\n";
private final String projectName;
private final int versionID;
@@ -28,7 +29,9 @@ public class SetProjectSQLUpdate implements SQLUpdate {
}
@Override
public void addParametersToStatement(PreparedStatement statement) throws SQLException {
public void addParametersToStatement(
PreparedStatement statement
) throws SQLException {
statement.setString(1, projectName);
statement.setInt(2, versionID);
}

View File

@@ -9,7 +9,7 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents;
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
import uk.ac.ic.wlgitbridge.git.util.RepositoryObjectTreeWalker;
import uk.ac.ic.wlgitbridge.util.Log;
import uk.ac.ic.wlgitbridge.util.Project;
@@ -62,7 +62,7 @@ public class GitProjectRepo implements ProjectRepo {
@Override
public Map<String, RawFile> getFiles()
throws IOException, GitUserException {
throws IOException, SizeLimitExceededException {
Preconditions.checkState(repository.isPresent());
return new RepositoryObjectTreeWalker(
repository.get()

View File

@@ -1,9 +1,8 @@
package uk.ac.ic.wlgitbridge.bridge.repo;
import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore;
import uk.ac.ic.wlgitbridge.data.filestore.GitDirectoryContents;
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
import java.io.IOException;
import java.util.Collection;
@@ -24,7 +23,8 @@ public interface ProjectRepo {
RepoStore repoStore
) throws IOException;
Map<String, RawFile> getFiles() throws IOException, GitUserException;
Map<String, RawFile> getFiles(
) throws IOException, SizeLimitExceededException;
Collection<String> commitAndGetMissing(
GitDirectoryContents gitDirectoryContents

View File

@@ -9,5 +9,13 @@ import java.util.Map;
* Created by winston on 20/08/2016.
*/
public interface ResourceCache {
RawFile get(String projectName, String url, String newPath, Map<String, RawFile> fileTable, Map<String, byte[]> fetchedUrls) throws IOException;
RawFile get(
String projectName,
String url,
String newPath,
Map<String, RawFile> fileTable,
Map<String, byte[]> fetchedUrls
) throws IOException;
}

View File

@@ -27,7 +27,13 @@ public class UrlResourceCache implements ResourceCache {
}
@Override
public RawFile get(String projectName, String url, String newPath, Map<String, RawFile> fileTable, Map<String, byte[]> fetchedUrls) throws IOException {
public RawFile get(
String projectName,
String url,
String newPath,
Map<String, RawFile> fileTable,
Map<String, byte[]> fetchedUrls
) throws IOException {
String path = dbStore.getPathForURLInProject(projectName, url);
byte[] contents;
if (path == null) {
@@ -42,8 +48,11 @@ public class UrlResourceCache implements ResourceCache {
RawFile rawFile = fileTable.get(path);
if (rawFile == null) {
Log.warn(
"File " + path + " was not in the current commit, or the git tree, yet path was not null. " +
"File url is: " + url
"File " + path
+ " was not in the current commit, "
+ "or the git tree, yet path was not null. "
+ "File url is: "
+ url
);
contents = fetch(projectName, url, path);
} else {
@@ -54,25 +63,42 @@ public class UrlResourceCache implements ResourceCache {
return new RepositoryFile(newPath, contents);
}
private byte[] fetch(String projectName, final String url, String path) throws FailedConnectionException {
private byte[] fetch(
String projectName,
final String url,
String path
) throws FailedConnectionException {
byte[] contents;
Log.info("GET -> " + url);
try {
contents = Request.httpClient.prepareGet(url).execute(new AsyncCompletionHandler<byte[]>() {
contents = Request.httpClient.prepareGet(url).execute(
new AsyncCompletionHandler<byte[]>() {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@Override
public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
public STATE onBodyPartReceived(
HttpResponseBodyPart bodyPart
) throws Exception {
bytes.write(bodyPart.getBodyPartBytes());
return STATE.CONTINUE;
}
@Override
public byte[] onCompleted(Response response) throws Exception {
public byte[] onCompleted(
Response response
) throws Exception {
byte[] data = bytes.toByteArray();
bytes.close();
Log.info(response.getStatusCode() + " " + response.getStatusText() + " (" + data.length + "B) -> " + url);
Log.info(
response.getStatusCode()
+ " "
+ response.getStatusText()
+ " ("
+ data.length
+ "B) -> "
+ url
);
return data;
}

View File

@@ -20,54 +20,99 @@ import java.util.*;
public class NetSnapshotAPI implements SnapshotAPI {
@Override
public Deque<Snapshot> getSnapshotsForProjectAfterVersion(Credential oauth2, String projectName, int version) throws FailedConnectionException, GitUserException {
List<SnapshotInfo> snapshotInfos = getSnapshotInfosAfterVersion(oauth2, projectName, version);
List<SnapshotData> snapshotDatas = getMatchingSnapshotData(oauth2, projectName, snapshotInfos);
LinkedList<Snapshot> snapshots = combine(snapshotInfos, snapshotDatas);
return snapshots;
public Deque<Snapshot> getSnapshotsForProjectAfterVersion(
Credential oauth2,
String projectName,
int version
) throws FailedConnectionException, GitUserException {
List<SnapshotInfo> snapshotInfos = getSnapshotInfosAfterVersion(
oauth2,
projectName,
version
);
List<SnapshotData> snapshotDatas = getMatchingSnapshotData(
oauth2,
projectName,
snapshotInfos
);
return combine(snapshotInfos, snapshotDatas);
}
private List<SnapshotInfo> getSnapshotInfosAfterVersion(Credential oauth2, String projectName, int version) throws FailedConnectionException, GitUserException {
SortedSet<SnapshotInfo> versions = new TreeSet<SnapshotInfo>();
private List<SnapshotInfo> getSnapshotInfosAfterVersion(
Credential oauth2,
String projectName,
int version
) throws FailedConnectionException, GitUserException {
SortedSet<SnapshotInfo> versions = new TreeSet<>();
GetDocRequest getDoc = new GetDocRequest(oauth2, projectName);
GetSavedVersRequest getSavedVers = new GetSavedVersRequest(oauth2, projectName);
GetSavedVersRequest getSavedVers = new GetSavedVersRequest(
oauth2,
projectName
);
getDoc.request();
getSavedVers.request();
GetDocResult latestDoc = getDoc.getResult();
int latest = latestDoc.getVersionID();
if (latest > version) {
for (SnapshotInfo snapshotInfo : getSavedVers.getResult().getSavedVers()) {
for (
SnapshotInfo snapshotInfo :
getSavedVers.getResult().getSavedVers()
) {
if (snapshotInfo.getVersionId() > version) {
versions.add(snapshotInfo);
}
}
versions.add(new SnapshotInfo(latest, latestDoc.getCreatedAt(), latestDoc.getName(), latestDoc.getEmail()));
versions.add(new SnapshotInfo(
latest,
latestDoc.getCreatedAt(),
latestDoc.getName(),
latestDoc.getEmail()
));
}
return new LinkedList<SnapshotInfo>(versions);
}
private List<SnapshotData> getMatchingSnapshotData(Credential oauth2, String projectName, List<SnapshotInfo> snapshotInfos) throws FailedConnectionException, ForbiddenException {
List<GetForVersionRequest> firedRequests = fireDataRequests(oauth2, projectName, snapshotInfos);
List<SnapshotData> snapshotDataList = new LinkedList<SnapshotData>();
private List<SnapshotData> getMatchingSnapshotData(
Credential oauth2,
String projectName,
List<SnapshotInfo> snapshotInfos
) throws FailedConnectionException, ForbiddenException {
List<GetForVersionRequest> firedRequests = fireDataRequests(
oauth2,
projectName,
snapshotInfos
);
List<SnapshotData> snapshotDataList = new ArrayList<>();
for (GetForVersionRequest fired : firedRequests) {
snapshotDataList.add(fired.getResult().getSnapshotData());
}
return snapshotDataList;
}
private List<GetForVersionRequest> fireDataRequests(Credential oauth2, String projectName, List<SnapshotInfo> snapshotInfos) {
List<GetForVersionRequest> requests = new LinkedList<GetForVersionRequest>();
private List<GetForVersionRequest> fireDataRequests(
Credential oauth2,
String projectName,
List<SnapshotInfo> snapshotInfos
) {
List<GetForVersionRequest> requests = new ArrayList<>();
for (SnapshotInfo snapshotInfo : snapshotInfos) {
GetForVersionRequest request = new GetForVersionRequest(oauth2, projectName, snapshotInfo.getVersionId());
GetForVersionRequest request = new GetForVersionRequest(
oauth2,
projectName,
snapshotInfo.getVersionId()
);
requests.add(request);
request.request();
}
return requests;
}
private LinkedList<Snapshot> combine(List<SnapshotInfo> snapshotInfos, List<SnapshotData> snapshotDatas) {
LinkedList<Snapshot> snapshots = new LinkedList<Snapshot>();
private Deque<Snapshot> combine(
List<SnapshotInfo> snapshotInfos,
List<SnapshotData> snapshotDatas
) {
Deque<Snapshot> snapshots = new LinkedList<>();
Iterator<SnapshotInfo> infos = snapshotInfos.iterator();
Iterator<SnapshotData> datas = snapshotDatas.iterator();
while (infos.hasNext()) {

View File

@@ -1,30 +1,20 @@
package uk.ac.ic.wlgitbridge.bridge.swap.job;
import java.io.IOException;
/**
* Created by winston on 24/08/2016.
*/
public class NoopSwapJob implements SwapJob {
@Override
public void start() {
}
public void start() {}
@Override
public void stop() {
}
public void stop() {}
@Override
public void evict(String projName) throws IOException {
}
public void evict(String projName) {}
@Override
public void restore(String projName) throws IOException {
}
public void restore(String projName) {}
}

View File

@@ -39,4 +39,5 @@ public interface SwapJob {
void evict(String projName) throws IOException;
void restore(String projName) throws IOException;
}

View File

@@ -1,7 +1,6 @@
package uk.ac.ic.wlgitbridge.bridge.swap.store;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
@@ -9,18 +8,14 @@ import java.io.InputStream;
*/
public class NoopSwapStore implements SwapStore {
public NoopSwapStore(SwapStoreConfig config) {
}
public NoopSwapStore(SwapStoreConfig __) {}
@Override
public void upload(
String projectName,
InputStream uploadStream,
long contentLength
) throws IOException {
}
) {}
@Override
public InputStream openDownloadStream(String projectName) {
@@ -28,8 +23,6 @@ public class NoopSwapStore implements SwapStore {
}
@Override
public void remove(String projectName) {
}
public void remove(String projectName) {}
}

View File

@@ -5,6 +5,6 @@ package uk.ac.ic.wlgitbridge.data;
*/
public interface LockAllWaiter {
public void threadsRemaining(int threads);
void threadsRemaining(int threads);
}

View File

@@ -1,10 +0,0 @@
package uk.ac.ic.wlgitbridge.data;
/**
* Created by Winston on 07/11/14.
*/
public class SnapshotFetcher {
}

View File

@@ -14,6 +14,7 @@ import java.util.Arrays;
public abstract class RawFile {
public abstract String getPath();
public abstract byte[] getContents();
public final void writeToDisk(File directory) throws IOException {

View File

@@ -1,9 +0,0 @@
package uk.ac.ic.wlgitbridge.data.model;
/**
* Created by Winston on 21/02/15.
*/
public class ResourceFetcher {
}

View File

@@ -8,6 +8,7 @@ import java.util.List;
public abstract class GitUserException extends Exception {
public abstract String getMessage();
public abstract List<String> getDescriptionLines();
}

View File

@@ -1,8 +0,0 @@
package uk.ac.ic.wlgitbridge.git.exception;
/**
* Created by Winston on 03/11/14.
*/
public class InvalidRootDirectoryPathException extends Exception {
}

View File

@@ -21,10 +21,12 @@ public class SizeLimitExceededException extends GitUserException {
@Override
public List<String> getDescriptionLines() {
String filename = path != null ? "File '" + path + "' is" : "There's a file";
String filename =
path != null ? "File '" + path + "' is" : "There's a file";
return Arrays.asList(
filename + " too large to push to " + Util.getServiceName() + " via git",
"the recommended maximum file size is 50 MiB"
filename + " too large to push to "
+ Util.getServiceName() + " via git",
"the recommended maximum file size is 50 MiB"
);
}

View File

@@ -7,5 +7,4 @@ import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource;
*/
public abstract class SnapshotAPIException
extends GitUserException
implements JSONSource {
}
implements JSONSource {}

View File

@@ -4,10 +4,10 @@ import com.google.api.client.auth.oauth2.Credential;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import uk.ac.ic.wlgitbridge.bridge.Bridge;
import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotAPI;
import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook;
import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet;
import uk.ac.ic.wlgitbridge.server.Oauth2Filter;
import uk.ac.ic.wlgitbridge.util.Util;
@@ -16,8 +16,15 @@ import javax.servlet.http.HttpServletRequest;
/**
* Created by Winston on 02/11/14.
*/
/* */
public class WLReceivePackFactory implements ReceivePackFactory<HttpServletRequest> {
/**
* One of the "big three" interfaces created by {@link WLGitServlet} to handle
* user Git requests.
*
* This class just puts a {@link WriteLatexPutHook} into the {@link ReceivePack}
* that it returns.
*/
public class WLReceivePackFactory
implements ReceivePackFactory<HttpServletRequest> {
private final Bridge bridge;
@@ -25,15 +32,39 @@ public class WLReceivePackFactory implements ReceivePackFactory<HttpServletReque
this.bridge = bridge;
}
/**
* Puts a {@link WriteLatexPutHook} into the returned {@link ReceivePack}.
*
* The {@link WriteLatexPutHook} needs our hostname, which we get from the
* original {@link HttpServletRequest}, used to provide a postback URL to
* the {@link SnapshotAPI}. We also give it the oauth2 that we injected in
* the {@link Oauth2Filter}, and the {@link Bridge}.
*
* At this point, the repository will have been synced to the latest on
* Overleaf, but it's possible that an update happens on Overleaf while our
* put hook is running. In this case, we fail, and the user tries again,
* triggering another sync, and so on.
* @param httpServletRequest the original request
* @param repository the JGit {@link Repository} provided by
* {@link WLRepositoryResolver}
* @return a correctly hooked {@link ReceivePack}
*/
@Override
public ReceivePack create(HttpServletRequest httpServletRequest, Repository repository) throws ServiceNotEnabledException, ServiceNotAuthorizedException {
Credential oauth2 = (Credential) httpServletRequest.getAttribute(Oauth2Filter.ATTRIBUTE_KEY);
public ReceivePack create(
HttpServletRequest httpServletRequest,
Repository repository
) {
Credential oauth2 = (Credential) httpServletRequest.getAttribute(
Oauth2Filter.ATTRIBUTE_KEY
);
ReceivePack receivePack = new ReceivePack(repository);
String hostname = Util.getPostbackURL();
if (hostname == null) {
hostname = httpServletRequest.getLocalName();
}
receivePack.setPreReceiveHook(new WriteLatexPutHook(bridge, hostname, oauth2));
receivePack.setPreReceiveHook(
new WriteLatexPutHook(bridge, hostname, oauth2)
);
return receivePack;
}

View File

@@ -6,10 +6,12 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import uk.ac.ic.wlgitbridge.bridge.Bridge;
import uk.ac.ic.wlgitbridge.bridge.repo.GitProjectRepo;
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook;
import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet;
import uk.ac.ic.wlgitbridge.server.GitBridgeServer;
import uk.ac.ic.wlgitbridge.server.Oauth2Filter;
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
import uk.ac.ic.wlgitbridge.util.Log;
@@ -21,6 +23,18 @@ import java.io.IOException;
/**
* Created by Winston on 02/11/14.
*/
/**
* One of the "big three" interfaces created by {@link WLGitServlet} to handle
* user Git requests.
*
* This class is used by all Git requests to resolve a project name to a
* JGit {@link Repository}, or fail by throwing an exception.
*
* It has a single method, {@link #open(HttpServletRequest, String)}, which
* calls into the {@link Bridge} to synchronise the project with Overleaf, i.e.
* bringing it onto disk and applying commits to it until it is up-to-date with
* Overleaf.
*/
public class WLRepositoryResolver
implements RepositoryResolver<HttpServletRequest> {
@@ -30,13 +44,42 @@ public class WLRepositoryResolver
this.bridge = bridge;
}
/**
* Calls into the Bridge to resolve a project name to a JGit
* {@link Repository}, or throw an exception.
*
* On success, the repository will have been brought onto disk and updated
* to the latest (synced).
*
* In the case of clones and fetches, upload packs are created from the
* returned JGit {@link Repository} by the {@link WLUploadPackFactory}.
*
* The project lock is acquired for this process so it can't be swapped out.
*
* However, it can still be swapped out between this and a Git push. The
* push would fail due to the project changed on Overleaf between the sync
* and the actual push to Overleaf (performed by the
* {@link WLReceivePackFactory} and {@link WriteLatexPutHook}. In this case,
* the user will have to try again (which prompts another update, etc. until
* this no longer happens).
* @param httpServletRequest The HttpServletRequest as required by the
* interface. We injected the oauth2 creds into it with
* {@link Oauth2Filter}, which was set up by the {@link GitBridgeServer}.
* @param name The name of the project
* @return the JGit {@link Repository}.
* @throws RepositoryNotFoundException If the project does not exist
* @throws ServiceNotAuthorizedException If the user did not auth when
* required to
* @throws ServiceMayNotContinueException If any other general user
* exception occurs that must be propogated back to the user, e.g.
* internal errors (IOException, etc), too large file, and so on.
*/
@Override
public Repository open(
HttpServletRequest httpServletRequest,
String name
) throws RepositoryNotFoundException,
ServiceNotAuthorizedException,
ServiceNotEnabledException,
ServiceMayNotContinueException {
Credential oauth2 = (Credential) httpServletRequest.getAttribute(
Oauth2Filter.ATTRIBUTE_KEY

View File

@@ -2,9 +2,8 @@ package uk.ac.ic.wlgitbridge.git.handler;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet;
import uk.ac.ic.wlgitbridge.util.Util;
import javax.servlet.http.HttpServletRequest;
@@ -12,11 +11,34 @@ import javax.servlet.http.HttpServletRequest;
/**
* Created by Winston on 02/11/14.
*/
public class WLUploadPackFactory implements UploadPackFactory<HttpServletRequest> {
/**
* One of the "big three" interfaces created by {@link WLGitServlet} to handle
* user Git requests.
*
* The actual class doesn't do much, and most of the work is done when the
* project name is being resolved by the {@link WLRepositoryResolver}.
*/
public class WLUploadPackFactory
implements UploadPackFactory<HttpServletRequest> {
/**
* This does nothing special. Synchronising the project with Overleaf will
* have been performed by {@link WLRepositoryResolver}.
* @param __ Not used, required by the {@link UploadPackFactory} interface
* @param repository The JGit repository provided by the
* {@link WLRepositoryResolver}
* @return the {@link UploadPack}, used by JGit to serve the request
*/
@Override
public UploadPack create(HttpServletRequest httpServletRequest, Repository repository) throws ServiceNotEnabledException, ServiceNotAuthorizedException {
public UploadPack create(
HttpServletRequest __,
Repository repository
) {
UploadPack uploadPack = new UploadPack(repository);
uploadPack.sendMessage("Downloading files from " + Util.getServiceName());
uploadPack.sendMessage(
"Downloading files from " + Util.getServiceName()
);
return uploadPack;
}
}

View File

@@ -9,6 +9,7 @@ import org.eclipse.jgit.transport.ReceivePack;
import uk.ac.ic.wlgitbridge.bridge.Bridge;
import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory;
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
import uk.ac.ic.wlgitbridge.git.handler.WLReceivePackFactory;
import uk.ac.ic.wlgitbridge.git.handler.hook.exception.ForcedPushException;
import uk.ac.ic.wlgitbridge.git.handler.hook.exception.WrongBranchException;
import uk.ac.ic.wlgitbridge.git.util.RepositoryObjectTreeWalker;
@@ -24,42 +25,80 @@ import java.util.Iterator;
/**
* Created by Winston on 03/11/14.
*/
/**
* Created by {@link WLReceivePackFactory} to update the {@link Bridge} for a
* user's Git push request, or fail with an error. The hook is able to approve
* or reject a request.
*/
public class WriteLatexPutHook implements PreReceiveHook {
private final Bridge bridge;
private final String hostname;
private final Credential oauth2;
public WriteLatexPutHook(Bridge bridge, String hostname, Credential oauth2) {
/**
* The constructor to use, which provides the hook with the {@link Bridge},
* the hostname (used to construct a URL to give to Overleaf to postback),
* and the oauth2 (used to authenticate with the Snapshot API).
* @param bridge the {@link Bridge}
* @param hostname the hostname used for postback from the Snapshot API
* @param oauth2 used to authenticate with the snapshot API, or null
*/
public WriteLatexPutHook(
Bridge bridge,
String hostname,
Credential oauth2
) {
this.bridge = bridge;
this.hostname = hostname;
this.oauth2 = oauth2;
}
@Override
public void onPreReceive(ReceivePack receivePack, Collection<ReceiveCommand> receiveCommands) {
public void onPreReceive(
ReceivePack receivePack,
Collection<ReceiveCommand> receiveCommands
) {
for (ReceiveCommand receiveCommand : receiveCommands) {
try {
handleReceiveCommand(oauth2, receivePack.getRepository(), receiveCommand);
handleReceiveCommand(
oauth2,
receivePack.getRepository(),
receiveCommand
);
} catch (IOException e) {
receivePack.sendError(e.getMessage());
receiveCommand.setResult(Result.REJECTED_OTHER_REASON, e.getMessage());
receiveCommand.setResult(
Result.REJECTED_OTHER_REASON,
e.getMessage()
);
} catch (OutOfDateException e) {
receiveCommand.setResult(Result.REJECTED_NONFASTFORWARD);
} catch (SnapshotPostException e) {
handleSnapshotPostException(receivePack, receiveCommand, e);
} catch (Throwable t) {
Log.warn("Throwable on pre receive: ", t);
handleSnapshotPostException(receivePack, receiveCommand, new InternalErrorException());
handleSnapshotPostException(
receivePack,
receiveCommand,
new InternalErrorException()
);
}
}
}
private void handleSnapshotPostException(ReceivePack receivePack, ReceiveCommand receiveCommand, SnapshotPostException e) {
private void handleSnapshotPostException(
ReceivePack receivePack,
ReceiveCommand receiveCommand,
SnapshotPostException e
) {
String message = e.getMessage();
receivePack.sendError(message);
StringBuilder msg = new StringBuilder();
for (Iterator<String> it = e.getDescriptionLines().iterator(); it.hasNext();) {
for (
Iterator<String> it = e.getDescriptionLines().iterator();
it.hasNext();
) {
String line = it.next();
msg.append("hint: ");
msg.append(line);
@@ -72,10 +111,14 @@ public class WriteLatexPutHook implements PreReceiveHook {
receiveCommand.setResult(Result.REJECTED_OTHER_REASON, message);
}
private void handleReceiveCommand(Credential oauth2, Repository repository, ReceiveCommand receiveCommand) throws IOException, GitUserException {
private void handleReceiveCommand(
Credential oauth2,
Repository repository,
ReceiveCommand receiveCommand
) throws IOException, GitUserException {
checkBranch(receiveCommand);
checkForcedPush(receiveCommand);
bridge.putDirectoryContentsToProjectWithName(
bridge.push(
oauth2,
repository.getWorkTree().getName(),
getPushedDirectoryContents(repository,
@@ -85,26 +128,41 @@ public class WriteLatexPutHook implements PreReceiveHook {
);
}
private void checkBranch(ReceiveCommand receiveCommand) throws WrongBranchException {
private void checkBranch(
ReceiveCommand receiveCommand
) throws WrongBranchException {
if (!receiveCommand.getRefName().equals("refs/heads/master")) {
throw new WrongBranchException();
}
}
private void checkForcedPush(ReceiveCommand receiveCommand) throws ForcedPushException {
if (receiveCommand.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
private void checkForcedPush(
ReceiveCommand receiveCommand
) throws ForcedPushException {
if (
receiveCommand.getType()
== ReceiveCommand.Type.UPDATE_NONFASTFORWARD
) {
throw new ForcedPushException();
}
}
private RawDirectory getPushedDirectoryContents(Repository repository, ReceiveCommand receiveCommand) throws IOException, GitUserException {
return new RepositoryObjectTreeWalker(repository,
receiveCommand.getNewId())
.getDirectoryContents();
private RawDirectory getPushedDirectoryContents(
Repository repository,
ReceiveCommand receiveCommand
) throws IOException, GitUserException {
return new RepositoryObjectTreeWalker(
repository,
receiveCommand.getNewId()
).getDirectoryContents();
}
private RawDirectory getOldDirectoryContents(Repository repository) throws IOException, GitUserException {
return new RepositoryObjectTreeWalker(repository).getDirectoryContents();
private RawDirectory getOldDirectoryContents(
Repository repository
) throws IOException, GitUserException {
return new RepositoryObjectTreeWalker(
repository
).getDirectoryContents();
}
}

View File

@@ -13,9 +13,12 @@ import java.util.List;
public class ForcedPushException extends SnapshotPostException {
private static final String[] DESCRIPTION_LINES = {
"You can't git push --force to a " + Util.getServiceName() + " project.",
"You can't git push --force to a "
+ Util.getServiceName()
+ " project.",
"Try to put your changes on top of the current head.",
"If everything else fails, delete and reclone your repository, make your changes, then push again."
"If everything else fails, delete and reclone your repository, "
+ "make your changes, then push again."
};
@Override
@@ -29,8 +32,6 @@ public class ForcedPushException extends SnapshotPostException {
}
@Override
public void fromJSON(JsonElement json) {
}
public void fromJSON(JsonElement json) {}
}

View File

@@ -3,22 +3,44 @@ package uk.ac.ic.wlgitbridge.git.servlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jgit.http.server.GitServlet;
import uk.ac.ic.wlgitbridge.bridge.Bridge;
import uk.ac.ic.wlgitbridge.git.exception.InvalidRootDirectoryPathException;
import uk.ac.ic.wlgitbridge.git.handler.WLReceivePackFactory;
import uk.ac.ic.wlgitbridge.git.handler.WLRepositoryResolver;
import uk.ac.ic.wlgitbridge.git.handler.WLUploadPackFactory;
import uk.ac.ic.wlgitbridge.server.GitBridgeServer;
import javax.servlet.ServletException;
/**
* Created by Winston on 02/11/14.
*/
/**
* This is the Servlet created by the {@link GitBridgeServer} that does all of
* the work in handling user Git requests and directing them to the
* {@link Bridge}.
*
* The {@link GitServlet} does all of the Git work, and these main three
* interfaces do all of the Git Bridge work:
*
* @see WLRepositoryResolver
* @see WLReceivePackFactory
* @see WLUploadPackFactory
*/
public class WLGitServlet extends GitServlet {
/**
* Constructor that sets all of the resolvers and factories for the
* {@link GitServlet}.
*
* Also needs to call init with a config ({@link WLGitServletConfig}, as
* required by the {@link GitServlet}.
* @param ctxHandler
* @param bridge
* @throws ServletException
*/
public WLGitServlet(
ServletContextHandler ctxHandler,
Bridge bridge
) throws ServletException, InvalidRootDirectoryPathException {
) throws ServletException {
setRepositoryResolver(new WLRepositoryResolver(bridge));
setReceivePackFactory(new WLReceivePackFactory(bridge));
setUploadPackFactory(new WLUploadPackFactory());

View File

@@ -8,7 +8,6 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import uk.ac.ic.wlgitbridge.data.filestore.RawDirectory;
import uk.ac.ic.wlgitbridge.data.filestore.RawFile;
import uk.ac.ic.wlgitbridge.data.filestore.RepositoryFile;
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
import uk.ac.ic.wlgitbridge.git.exception.SizeLimitExceededException;
import java.io.IOException;
@@ -23,24 +22,36 @@ public class RepositoryObjectTreeWalker {
private final TreeWalk treeWalk;
private final Repository repository;
public RepositoryObjectTreeWalker(Repository repository, ObjectId objectId) throws IOException {
public RepositoryObjectTreeWalker(
Repository repository,
ObjectId objectId
) throws IOException {
treeWalk = initTreeWalk(repository, objectId);
this.repository = repository;
}
public RepositoryObjectTreeWalker(Repository repository) throws IOException {
public RepositoryObjectTreeWalker(
Repository repository
) throws IOException {
this(repository, 0);
}
public RepositoryObjectTreeWalker(Repository repository, int fromHead) throws IOException {
public RepositoryObjectTreeWalker(
Repository repository,
int fromHead
) throws IOException {
this(repository, repository.resolve("HEAD~" + fromHead));
}
public RawDirectory getDirectoryContents() throws IOException, GitUserException {
public RawDirectory getDirectoryContents(
) throws IOException, SizeLimitExceededException {
return new RawDirectory(walkGitObjectTree());
}
private TreeWalk initTreeWalk(Repository repository, ObjectId objectId) throws IOException {
private TreeWalk initTreeWalk(
Repository repository,
ObjectId objectId
) throws IOException {
if (objectId == null) {
return null;
}
@@ -51,8 +62,9 @@ public class RepositoryObjectTreeWalker {
return treeWalk;
}
private Map<String, RawFile> walkGitObjectTree() throws IOException, GitUserException {
Map<String, RawFile> fileContentsTable = new HashMap<String, RawFile>();
private Map<String, RawFile> walkGitObjectTree(
) throws IOException, SizeLimitExceededException {
Map<String, RawFile> fileContentsTable = new HashMap<>();
if (treeWalk == null) {
return fileContentsTable;
}
@@ -60,10 +72,11 @@ public class RepositoryObjectTreeWalker {
String path = treeWalk.getPathString();
try {
byte[] content = repository.open(treeWalk.getObjectId(0)).getBytes();
byte[] content = repository.open(
treeWalk.getObjectId(0)
).getBytes();
fileContentsTable.put(path, new RepositoryFile(path, content));
}
catch (LargeObjectException e) {
} catch (LargeObjectException e) {
throw new SizeLimitExceededException(path);
}
}

View File

@@ -20,20 +20,23 @@ import java.util.regex.Pattern;
* Requests must include the postback key.
*/
public class FileHandler extends ResourceHandler {
private static final Logger LOG = LoggerFactory.getLogger(FileHandler.class);
private static final Logger LOG
= LoggerFactory.getLogger(FileHandler.class);
private final Bridge writeLatexDataSource;
private final Bridge bridge;
private final Pattern DOC_KEY_PATTERN = Pattern.compile("^/(\\w+)/.+$");
public FileHandler(Bridge writeLatexDataSource) {
this.writeLatexDataSource = writeLatexDataSource;
public FileHandler(Bridge bridge) {
this.bridge = bridge;
}
@Override
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
public void handle(
String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response
) throws IOException, ServletException {
if (!"GET".equals(baseRequest.getMethod())) return;
LOG.info("GET <- {}", baseRequest.getRequestURI());
@@ -45,12 +48,17 @@ public class FileHandler extends ResourceHandler {
if (apiKey == null) return;
try {
writeLatexDataSource.checkPostbackKey(docKey, apiKey);
bridge.checkPostbackKey(docKey, apiKey);
} catch (InvalidPostbackKeyException e) {
LOG.warn("INVALID POST BACK KEY: docKey={} apiKey={}", docKey, apiKey);
LOG.warn(
"INVALID POST BACK KEY: docKey={} apiKey={}",
docKey,
apiKey
);
return;
}
super.handle(target, baseRequest, request, response);
}
}

View File

@@ -14,7 +14,6 @@ import uk.ac.ic.wlgitbridge.bridge.db.sqlite.SqliteDBStore;
import uk.ac.ic.wlgitbridge.bridge.repo.FSGitRepoStore;
import uk.ac.ic.wlgitbridge.bridge.repo.RepoStore;
import uk.ac.ic.wlgitbridge.bridge.swap.store.SwapStore;
import uk.ac.ic.wlgitbridge.git.exception.InvalidRootDirectoryPathException;
import uk.ac.ic.wlgitbridge.git.servlet.WLGitServlet;
import uk.ac.ic.wlgitbridge.snapshot.base.SnapshotAPIRequest;
import uk.ac.ic.wlgitbridge.util.Log;
@@ -47,7 +46,7 @@ public class GitBridgeServer {
public GitBridgeServer(
Config config
) throws ServletException, InvalidRootDirectoryPathException {
) throws ServletException {
org.eclipse.jetty.util.log.Log.setLog(new NullLogger());
this.port = config.getPort();
this.rootGitDirectoryPath = config.getRootGitDirectory();
@@ -107,7 +106,7 @@ public class GitBridgeServer {
private void configureJettyServer(
Config config
) throws ServletException, InvalidRootDirectoryPathException {
) throws ServletException {
HandlerCollection handlers = new HandlerList();
handlers.addHandler(initApiHandler());
handlers.addHandler(initGitHandler(config));
@@ -129,7 +128,7 @@ public class GitBridgeServer {
private Handler initGitHandler(
Config config
) throws ServletException, InvalidRootDirectoryPathException {
) throws ServletException {
final ServletContextHandler servletContextHandler =
new ServletContextHandler(ServletContextHandler.SESSIONS);
if (config.isUsingOauth2()) {
@@ -160,4 +159,5 @@ public class GitBridgeServer {
);
return resourceHandler;
}
}

View File

@@ -2,13 +2,8 @@ package uk.ac.ic.wlgitbridge.server;
import com.google.api.client.auth.oauth2.*;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.server.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.ic.wlgitbridge.application.config.Oauth2;
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest;
@@ -37,26 +32,40 @@ public class Oauth2Filter implements Filter {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void init(FilterConfig filterConfig) {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
String project = Util.removeAllSuffixes(((Request) servletRequest).getRequestURI().split("/")[1], ".git");
public void doFilter(
ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain
) throws IOException, ServletException {
String project = Util.removeAllSuffixes(
((Request) servletRequest).getRequestURI().split("/")[1],
".git"
);
GetDocRequest doc = new GetDocRequest(project);
doc.request();
try {
doc.getResult();
} catch (ForbiddenException e) {
getAndInjectCredentials(servletRequest, servletResponse, filterChain);
getAndInjectCredentials(
servletRequest,
servletResponse,
filterChain
);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
private void getAndInjectCredentials(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// TODO: this is ridiculous. Check for error cases first, then return/throw
// TODO: also, use an Optional credential, since we treat it as optional
private void getAndInjectCredentials(
ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain
) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
@@ -67,26 +76,47 @@ public class Oauth2Filter implements Filter {
String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
try {
String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
String credentials = new String(
Base64.decodeBase64(st.nextToken()),
"UTF-8"
);
String[] split = credentials.split(":");
if (split.length == 2) {
String username = split[0];
String password = split[1];
String accessToken = null;
try {
accessToken = new PasswordTokenRequest(Instance.httpTransport, Instance.jsonFactory, new GenericUrl(oauth2.getOauth2Server() + "/oauth/token"), username, password)
.setClientAuthentication(new ClientParametersAuthentication(oauth2.getOauth2ClientID(), oauth2.getOauth2ClientSecret()))
accessToken = new PasswordTokenRequest(
Instance.httpTransport,
Instance.jsonFactory,
new GenericUrl(
oauth2.getOauth2Server()
+ "/oauth/token"
),
username,
password
).setClientAuthentication(
new ClientParametersAuthentication(
oauth2.getOauth2ClientID(),
oauth2.getOauth2ClientSecret()
)
)
.execute().getAccessToken();
} catch (TokenResponseException e) {
unauthorized(response);
return;
}
final Credential cred = new Credential.Builder(BearerToken.authorizationHeaderAccessMethod())
.build();
final Credential cred = new Credential.Builder(
BearerToken.authorizationHeaderAccessMethod(
)
).build();
cred.setAccessToken(accessToken);
servletRequest.setAttribute(ATTRIBUTE_KEY, cred);
filterChain.doFilter(servletRequest, servletResponse);
filterChain.doFilter(
servletRequest,
servletResponse
);
} else {
unauthorized(response);
}
@@ -101,22 +131,35 @@ public class Oauth2Filter implements Filter {
}
@Override
public void destroy() {
}
public void destroy() {}
private void unauthorized(ServletResponse servletResponse) throws IOException {
private void unauthorized(
ServletResponse servletResponse
) throws IOException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setContentType("text/plain");
response.setHeader("WWW-Authenticate", "Basic realm=\"Git Bridge\"");
response.setStatus(401);
PrintWriter w = response.getWriter();
w.println("Please sign in using your email address and Overleaf password.");
w.println(
"Please sign in using your email address and Overleaf password."
);
w.println();
w.println("*Note*: if you sign in to Overleaf using another provider, such ");
w.println("as Google or Twitter, you need to set a password on your Overleaf ");
w.println("account first. Please see https://www.overleaf.com/blog/195 for ");
w.println(
"*Note*: if you sign in to Overleaf using another provider, "
+ "such "
);
w.println(
"as Google or Twitter, you need to set a password "
+ "on your Overleaf "
);
w.println(
"account first. "
+ "Please see https://www.overleaf.com/blog/195 for "
);
w.println("more information.");
w.close();
}
}

View File

@@ -26,7 +26,12 @@ public class PostbackContents implements JSONSource {
private int versionID;
private SnapshotPostException exception;
public PostbackContents(Bridge bridge, String projectName, String postbackKey, String contents) {
public PostbackContents(
Bridge bridge,
String projectName,
String postbackKey,
String contents
) {
this.bridge = bridge;
this.projectName = projectName;
this.postbackKey = postbackKey;
@@ -43,9 +48,17 @@ public class PostbackContents implements JSONSource {
public void processPostback() throws UnexpectedPostbackException {
if (exception == null) {
bridge.postbackReceivedSuccessfully(projectName, postbackKey, versionID);
bridge.postbackReceivedSuccessfully(
projectName,
postbackKey,
versionID
);
} else {
bridge.postbackReceivedWithException(projectName, postbackKey, exception);
bridge.postbackReceivedWithException(
projectName,
postbackKey,
exception
);
}
}
@@ -63,7 +76,10 @@ public class PostbackContents implements JSONSource {
private void setException(JsonObject responseObject, String code) {
try {
exception = snapshotPostExceptionBuilder.build(code, responseObject);
exception = snapshotPostExceptionBuilder.build(
code,
responseObject
);
} catch (UnexpectedPostbackException e) {
throw new RuntimeException(e);
}

View File

@@ -26,10 +26,18 @@ public class PostbackHandler extends AbstractHandler {
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
public void handle(
String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response
) throws IOException, ServletException {
try {
if (request.getMethod().equals("POST") && target.endsWith("postback")) {
if (
request.getMethod().equals("POST")
&& target.endsWith("postback")
) {
response.setContentType("application/json");
String contents = Util.getContentsOfReader(request.getReader());
String[] parts = target.split("/");
@@ -38,8 +46,17 @@ public class PostbackHandler extends AbstractHandler {
}
String projectName = parts[1];
String postbackKey = parts[2];
Log.info(baseRequest.getMethod() + " <- " + baseRequest.getHttpURI());
PostbackContents postbackContents = new PostbackContents(bridge, projectName, postbackKey, contents);
Log.info(
baseRequest.getMethod()
+ " <- "
+ baseRequest.getHttpURI()
);
PostbackContents postbackContents = new PostbackContents(
bridge,
projectName,
postbackKey,
contents
);
JsonObject body = new JsonObject();
try {

View File

@@ -10,10 +10,9 @@ import java.util.List;
* Created by winston on 25/10/15.
*/
public class ForbiddenException extends SnapshotAPIException {
@Override
public void fromJSON(JsonElement json) {
}
@Override
public void fromJSON(JsonElement json) {}
@Override
public String getMessage() {

View File

@@ -7,6 +7,6 @@ import com.google.gson.JsonElement;
*/
public interface JSONSource {
public abstract void fromJSON(JsonElement json);
void fromJSON(JsonElement json);
}

View File

@@ -2,9 +2,7 @@ package uk.ac.ic.wlgitbridge.snapshot.base;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.http.BasicAuthentication;
import com.google.api.client.http.HttpExecuteInterceptor;
import com.google.api.client.http.HttpRequest;
import com.ning.http.client.Realm;
import java.io.IOException;
@@ -20,22 +18,26 @@ public abstract class SnapshotAPIRequest<T extends Result> extends Request<T> {
private final Credential oauth2;
public SnapshotAPIRequest(String projectName, String apiCall, Credential oauth2) {
public SnapshotAPIRequest(
String projectName,
String apiCall,
Credential oauth2
) {
super(BASE_URL + projectName + apiCall);
this.oauth2 = oauth2;
}
@Override
protected void onBeforeRequest(HttpRequest request) throws IOException {
protected void onBeforeRequest(
HttpRequest request
) throws IOException {
if (oauth2 != null) {
request.setInterceptor(new HttpExecuteInterceptor() {
@Override
public void intercept(HttpRequest request) throws IOException {
new BasicAuthentication(USERNAME, PASSWORD).intercept(request);
oauth2.intercept(request);
}
request.setInterceptor(request1 -> {
new BasicAuthentication(
USERNAME,
PASSWORD
).intercept(request1);
oauth2.intercept(request1);
});
}
}

View File

@@ -27,7 +27,9 @@ public class GetDocRequest extends SnapshotAPIRequest<GetDocResult> {
}
@Override
protected GetDocResult parseResponse(JsonElement json) throws FailedConnectionException {
protected GetDocResult parseResponse(
JsonElement json
) throws FailedConnectionException {
return new GetDocResult(this, json);
}

View File

@@ -24,11 +24,20 @@ public class GetDocResult extends Result {
private SnapshotAPIException exception;
private ForbiddenException forbidden;
public GetDocResult(Request request, JsonElement json) throws FailedConnectionException {
public GetDocResult(
Request request,
JsonElement json
) throws FailedConnectionException {
super(request, json);
}
public GetDocResult(JsonElement error, int versionID, String createdAt, String email, String name) {
public GetDocResult(
JsonElement error,
int versionID,
String createdAt,
String email,
String name
) {
if (error == null) {
this.error = -1;
} else {
@@ -75,7 +84,9 @@ public class GetDocResult extends Result {
exception = new InvalidProjectException();
break;
default:
throw new IllegalArgumentException("unknown get doc error code");
throw new IllegalArgumentException(
"unknown get doc error code"
);
}
} else {
versionID = jsonObject.get("latestVerId").getAsInt();

View File

@@ -1,29 +0,0 @@
package uk.ac.ic.wlgitbridge.snapshot.getdoc.exception;
import com.google.gson.JsonElement;
import uk.ac.ic.wlgitbridge.snapshot.push.exception.SnapshotPostException;
import java.util.Arrays;
import java.util.List;
/**
* Created by Winston on 20/02/15.
*/
public class ProtectedProjectException extends SnapshotPostException {
@Override
public String getMessage() {
return "Your project is protected, and can't be cloned (yet).";
}
@Override
public List<String> getDescriptionLines() {
return Arrays.asList("You can't currently clone a protected project.");
}
@Override
public void fromJSON(JsonElement json) {
}
}

View File

@@ -9,13 +9,18 @@ import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException;
/**
* Created by Winston on 06/11/14.
*/
public class GetForVersionRequest extends SnapshotAPIRequest<GetForVersionResult> {
public class GetForVersionRequest
extends SnapshotAPIRequest<GetForVersionResult> {
public static final String API_CALL = "/snapshots";
private int versionID;
public GetForVersionRequest(Credential oauth2, String projectName, int versionID) {
public GetForVersionRequest(
Credential oauth2,
String projectName,
int versionID
) {
super(projectName, API_CALL + "/" + versionID, oauth2);
this.versionID = versionID;
}
@@ -26,7 +31,9 @@ public class GetForVersionRequest extends SnapshotAPIRequest<GetForVersionResult
}
@Override
protected GetForVersionResult parseResponse(JsonElement json) throws FailedConnectionException {
protected GetForVersionResult parseResponse(
JsonElement json
) throws FailedConnectionException {
return new GetForVersionResult(this, json);
}

View File

@@ -5,7 +5,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import uk.ac.ic.wlgitbridge.snapshot.base.JSONSource;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
/**
@@ -20,12 +20,15 @@ public class SnapshotData implements JSONSource {
private List<SnapshotAttachment> atts;
public SnapshotData(JsonElement json) {
srcs = new LinkedList<SnapshotFile>();
atts = new LinkedList<SnapshotAttachment>();
srcs = new ArrayList<>();
atts = new ArrayList<>();
fromJSON(json);
}
public SnapshotData(List<SnapshotFile> srcs, List<SnapshotAttachment> atts) {
public SnapshotData(
List<SnapshotFile> srcs,
List<SnapshotAttachment> atts
) {
this.srcs = srcs;
this.atts = atts;
}
@@ -47,8 +50,12 @@ public class SnapshotData implements JSONSource {
@Override
public void fromJSON(JsonElement json) {
populateSrcs(json.getAsJsonObject().get(JSON_KEY_SRCS).getAsJsonArray());
populateAtts(json.getAsJsonObject().get(JSON_KEY_ATTS).getAsJsonArray());
populateSrcs(
json.getAsJsonObject().get(JSON_KEY_SRCS).getAsJsonArray()
);
populateAtts(
json.getAsJsonObject().get(JSON_KEY_ATTS).getAsJsonArray()
);
}
private void populateSrcs(JsonArray jsonArray) {
@@ -70,4 +77,5 @@ public class SnapshotData implements JSONSource {
public List<SnapshotAttachment> getAtts() {
return atts;
}
}

View File

@@ -9,7 +9,8 @@ import uk.ac.ic.wlgitbridge.snapshot.base.HTTPMethod;
/**
* Created by Winston on 06/11/14.
*/
public class GetSavedVersRequest extends SnapshotAPIRequest<GetSavedVersResult> {
public class GetSavedVersRequest
extends SnapshotAPIRequest<GetSavedVersResult> {
public static final String API_CALL = "/saved_vers";
@@ -23,7 +24,9 @@ public class GetSavedVersRequest extends SnapshotAPIRequest<GetSavedVersResult>
}
@Override
protected GetSavedVersResult parseResponse(JsonElement json) throws FailedConnectionException {
protected GetSavedVersResult parseResponse(
JsonElement json
) throws FailedConnectionException {
return new GetSavedVersResult(this, json);
}

View File

@@ -4,11 +4,11 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import uk.ac.ic.wlgitbridge.snapshot.base.Result;
import uk.ac.ic.wlgitbridge.snapshot.base.Request;
import uk.ac.ic.wlgitbridge.snapshot.base.Result;
import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
/**
@@ -18,7 +18,10 @@ public class GetSavedVersResult extends Result {
private List<SnapshotInfo> savedVers;
public GetSavedVersResult(Request request, JsonElement json) throws FailedConnectionException {
public GetSavedVersResult(
Request request,
JsonElement json
) throws FailedConnectionException {
super(request, json);
}
@@ -46,9 +49,14 @@ public class GetSavedVersResult extends Result {
@Override
public void fromJSON(JsonElement json) {
savedVers = new LinkedList<SnapshotInfo>();
savedVers = new ArrayList<>();
for (JsonElement elem : json.getAsJsonArray()) {
savedVers.add(new Gson().fromJson(elem.getAsJsonObject(), SnapshotInfo.class));
savedVers.add(
new Gson().fromJson(
elem.getAsJsonObject(),
SnapshotInfo.class
)
);
}
}

View File

@@ -12,11 +12,28 @@ public class SnapshotInfo implements Comparable<SnapshotInfo> {
private WLUser user;
private String createdAt;
public SnapshotInfo(int versionID, String createdAt, String name, String email) {
this(versionID, "Update on " + Util.getServiceName() + ".", email, name, createdAt);
public SnapshotInfo(
int versionID,
String createdAt,
String name,
String email
) {
this(
versionID,
"Update on " + Util.getServiceName() + ".",
email,
name,
createdAt
);
}
public SnapshotInfo(int versionID, String comment, String email, String name, String createdAt) {
public SnapshotInfo(
int versionID,
String comment,
String email,
String name,
String createdAt
) {
versionId = versionID;
this.comment = comment;
user = new WLUser(name, email);
@@ -52,4 +69,5 @@ public class SnapshotInfo implements Comparable<SnapshotInfo> {
public int compareTo(SnapshotInfo o) {
return Integer.compare(versionId, o.versionId);
}
}

View File

@@ -20,7 +20,9 @@ public class WLUser {
this.email = email;
} else {
this.name = "Anonymous";
this.email = "anonymous@" + Util.getServiceName().toLowerCase() + ".com";
this.email = "anonymous@"
+ Util.getServiceName().toLowerCase()
+ ".com";
}
}

View File

@@ -30,8 +30,9 @@ public class PostbackManager {
this(new SecureRandom());
}
public int waitForVersionIdOrThrow(String projectName)
throws SnapshotPostException {
public int waitForVersionIdOrThrow(
String projectName
) throws SnapshotPostException {
try {
PostbackPromise postbackPromise =
postbackContentsTable.get(projectName);
@@ -42,19 +43,21 @@ public class PostbackManager {
}
}
public void postVersionIDForProject(String projectName,
int versionID,
String postbackKey)
throws UnexpectedPostbackException {
public void postVersionIDForProject(
String projectName,
int versionID,
String postbackKey
) throws UnexpectedPostbackException {
getPostbackForProject(
projectName
).receivedVersionID(versionID, postbackKey);
}
public void postExceptionForProject(String projectName,
SnapshotPostException exception,
String postbackKey)
throws UnexpectedPostbackException {
public void postExceptionForProject(
String projectName,
SnapshotPostException exception,
String postbackKey
) throws UnexpectedPostbackException {
getPostbackForProject(
projectName
).receivedException(exception, postbackKey);
@@ -80,7 +83,8 @@ public class PostbackManager {
throws InvalidPostbackKeyException {
PostbackPromise postbackPromise = postbackContentsTable.get(projectName);
if (postbackPromise == null) {
throw new InvalidPostbackKeyException(); // project not found; can't check key
// project not found; can't check key
throw new InvalidPostbackKeyException();
} else {
postbackPromise.checkPostbackKey(postbackKey);
}

View File

@@ -64,7 +64,10 @@ public class PostbackPromise {
}
}
public void receivedException(SnapshotPostException exception, String postbackKey) {
public void receivedException(
SnapshotPostException exception,
String postbackKey
) {
lock.lock();
try {
if (postbackKey.equals(this.postbackKey)) {
@@ -77,7 +80,9 @@ public class PostbackPromise {
}
}
public void checkPostbackKey(String postbackKey) throws InvalidPostbackKeyException {
public void checkPostbackKey(
String postbackKey
) throws InvalidPostbackKeyException {
if (!postbackKey.equals(this.postbackKey)) {
throw new InvalidPostbackKeyException();
}

View File

@@ -17,7 +17,11 @@ public class PushRequest extends SnapshotAPIRequest<PushResult> {
private final CandidateSnapshot candidateSnapshot;
private final String postbackKey;
public PushRequest(Credential oauth2, CandidateSnapshot candidateSnapshot, String postbackKey) {
public PushRequest(
Credential oauth2,
CandidateSnapshot candidateSnapshot,
String postbackKey
) {
super(candidateSnapshot.getProjectName(), API_CALL, oauth2);
this.candidateSnapshot = candidateSnapshot;
this.postbackKey = postbackKey;
@@ -34,7 +38,9 @@ public class PushRequest extends SnapshotAPIRequest<PushResult> {
}
@Override
protected PushResult parseResponse(JsonElement json) throws FailedConnectionException {
protected PushResult parseResponse(
JsonElement json
) throws FailedConnectionException {
return new PushResult(this, json);
}

View File

@@ -14,7 +14,10 @@ public class PushResult extends Result {
private boolean success;
public PushResult(Request request, JsonElement json) throws FailedConnectionException {
public PushResult(
Request request,
JsonElement json
) throws FailedConnectionException {
super(request, json);
}

View File

@@ -18,13 +18,13 @@ public class InternalErrorException extends SevereSnapshotPostException {
@Override
public List<String> getDescriptionLines() {
return Arrays.asList("There was an internal error with the Git server.",
"Please contact " + Util.getServiceName() + ".");
return Arrays.asList(
"There was an internal error with the Git server.",
"Please contact " + Util.getServiceName() + "."
);
}
@Override
public void fromJSON(JsonElement json) {
}
public void fromJSON(JsonElement json) {}
}

View File

@@ -5,7 +5,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import uk.ac.ic.wlgitbridge.util.Util;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
/**
@@ -31,16 +31,24 @@ public class InvalidFilesException extends SnapshotPostException {
@Override
public void fromJSON(JsonElement json) {
descriptionLines = new LinkedList<String>();
JsonArray errors = json.getAsJsonObject().get("errors").getAsJsonArray();
descriptionLines.add("You have " + errors.size() + " invalid files in your " + Util.getServiceName() + " project:");
descriptionLines = new ArrayList<>();
JsonArray errors =
json.getAsJsonObject().get("errors").getAsJsonArray();
descriptionLines.add(
"You have "
+ errors.size()
+ " invalid files in your "
+ Util.getServiceName()
+ " project:"
);
for (JsonElement error : errors) {
descriptionLines.add(describeError(error.getAsJsonObject()));
}
}
private String describeError(JsonObject jsonObject) {
return jsonObject.get("file").getAsString() + " (" + describeFile(jsonObject) + ")";
return jsonObject.get("file").getAsString()
+ " (" + describeFile(jsonObject) + ")";
}
private String describeFile(JsonObject file) {

View File

@@ -3,5 +3,4 @@ package uk.ac.ic.wlgitbridge.snapshot.push.exception;
/**
* Created by Winston on 04/12/14.
*/
public class InvalidPostbackKeyException extends Exception {
}
public class InvalidPostbackKeyException extends Exception {}

View File

@@ -30,7 +30,8 @@ public class InvalidProjectException extends SnapshotPostException {
@Override
public void fromJSON(JsonElement json) {
descriptionLines = new LinkedList<String>();
JsonArray errors = json.getAsJsonObject().get("errors").getAsJsonArray();
JsonArray errors =
json.getAsJsonObject().get("errors").getAsJsonArray();
for (JsonElement error : errors) {
descriptionLines.add(error.getAsString());
}

View File

@@ -15,9 +15,7 @@ public class OutOfDateException extends SnapshotPostException {
super(json);
}
public OutOfDateException() {
}
public OutOfDateException() {}
@Override
public String getMessage() {
@@ -30,8 +28,6 @@ public class OutOfDateException extends SnapshotPostException {
}
@Override
public void fromJSON(JsonElement json) {
}
public void fromJSON(JsonElement json) {}
}

View File

@@ -18,12 +18,15 @@ public class PostbackTimeoutException extends SevereSnapshotPostException {
@Override
public List<String> getDescriptionLines() {
return Arrays.asList("The " + Util.getServiceName() + " server is currently unavailable.", "Please try again later.");
return Arrays.asList(
"The "
+ Util.getServiceName()
+ " server is currently unavailable.",
"Please try again later."
);
}
@Override
public void fromJSON(JsonElement json) {
}
public void fromJSON(JsonElement json) {}
}

View File

@@ -8,9 +8,7 @@ import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException;
*/
public abstract class SnapshotPostException extends SnapshotAPIException {
public SnapshotPostException() {
}
public SnapshotPostException() {}
public SnapshotPostException(JsonElement jsonElement) {
fromJSON(jsonElement);

View File

@@ -12,7 +12,10 @@ public class SnapshotPostExceptionBuilder {
private static final String CODE_ERROR_INVALID_PROJECT = "invalidProject";
private static final String CODE_ERROR_UNKNOWN = "error";
public SnapshotPostException build(String errorCode, JsonObject json) throws UnexpectedPostbackException {
public SnapshotPostException build(
String errorCode,
JsonObject json
) throws UnexpectedPostbackException {
if (errorCode.equals(CODE_ERROR_OUT_OF_DATE)) {
return new OutOfDateException(json);
} else if (errorCode.equals(CODE_ERROR_INVALID_FILES)) {

View File

@@ -13,7 +13,8 @@ import java.util.List;
public class UnexpectedErrorException extends SevereSnapshotPostException {
private static final String[] DESCRIPTION_LINES = {
"There was an internal error with the " + Util.getServiceName() + " server.",
"There was an internal error with the "
+ Util.getServiceName() + " server.",
"Please contact " + Util.getServiceName() + "."
};
@@ -32,8 +33,6 @@ public class UnexpectedErrorException extends SevereSnapshotPostException {
}
@Override
public void fromJSON(JsonElement json) {
}
public void fromJSON(JsonElement json) {}
}

View File

@@ -3,5 +3,4 @@ package uk.ac.ic.wlgitbridge.snapshot.push.exception;
/**
* Created by Winston on 17/11/14.
*/
public class UnexpectedPostbackException extends Exception {
}
public class UnexpectedPostbackException extends Exception {}

View File

@@ -13,8 +13,17 @@ import java.io.FileNotFoundException;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
MockSnapshotServer server = new MockSnapshotServer(60000, new File("/Users/Roxy/Code/java/writelatex-git-bridge"));
server.setState(new SnapshotAPIStateBuilder(new FileInputStream(new File("/Users/Roxy/Desktop/state.json"))).build());
MockSnapshotServer server = new MockSnapshotServer(
60000,
new File("/Users/Roxy/Code/java/writelatex-git-bridge")
);
server.setState(
new SnapshotAPIStateBuilder(
new FileInputStream(
new File("/Users/Roxy/Desktop/state.json")
)
).build()
);
server.start();
}

View File

@@ -1,10 +1,15 @@
package uk.ac.ic.wlgitbridge.snapshot.servermock.response;
import uk.ac.ic.wlgitbridge.snapshot.servermock.exception.InvalidAPICallException;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getdoc.SnapshotGetDocResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getforver.SnapshotGetForVerResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getsavedver.SnapshotGetSavedVersResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.SnapshotPushResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.exception
.InvalidAPICallException;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getdoc
.SnapshotGetDocResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getforver
.SnapshotGetForVerResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.getsavedver
.SnapshotGetSavedVersResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push
.SnapshotPushResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.state.SnapshotAPIState;
/**
@@ -14,7 +19,10 @@ public class SnapshotResponseBuilder {
private SnapshotAPIState state;
public SnapshotResponse buildWithTarget(String target, String method) throws InvalidAPICallException {
public SnapshotResponse buildWithTarget(
String target,
String method
) throws InvalidAPICallException {
checkPrefix(target);
return parseTarget(target, target.split("/"), method);
}
@@ -25,23 +33,38 @@ public class SnapshotResponseBuilder {
}
}
private SnapshotResponse parseTarget(String target, String[] parts, String method) throws InvalidAPICallException {
private SnapshotResponse parseTarget(
String target,
String[] parts,
String method
) throws InvalidAPICallException {
String projectName = parts[4];
if (parts.length == 5) {
if (method.equals("GET")) {
return new SnapshotGetDocResponse(state.getStateForGetDoc(projectName));
return new SnapshotGetDocResponse(
state.getStateForGetDoc(projectName)
);
}
} else if (parts.length == 6) {
String type = parts[5];
if (type.equals("snapshots") && method.equals("POST")) {
return new SnapshotPushResponse(state.getStateForPush(projectName), state.getStateForPostback(projectName));
return new SnapshotPushResponse(
state.getStateForPush(projectName),
state.getStateForPostback(projectName)
);
} else if (type.equals("saved_vers") && method.equals("GET")) {
return new SnapshotGetSavedVersResponse(state.getStateForGetSavedVers(projectName));
return new SnapshotGetSavedVersResponse(
state.getStateForGetSavedVers(projectName)
);
}
} else if (parts.length == 7) {
if (parts[5].equals("snapshots") && method.equals("GET")) {
try {
return new SnapshotGetForVerResponse(state.getStateForGetForVers(projectName, Integer.parseInt(parts[6])));
return new SnapshotGetForVerResponse(
state.getStateForGetForVers(
projectName, Integer.parseInt(parts[6])
)
);
} catch (NumberFormatException e) {
}

View File

@@ -12,7 +12,10 @@ public class SnapshotPushResponse extends SnapshotResponse {
private final SnapshotPushResult stateForPush;
private final SnapshotPostbackRequest stateForPostback;
public SnapshotPushResponse(SnapshotPushResult stateForPush, SnapshotPostbackRequest stateForPostback) {
public SnapshotPushResponse(
SnapshotPushResult stateForPush,
SnapshotPostbackRequest stateForPostback
) {
this.stateForPush = stateForPush;
this.stateForPostback = stateForPostback;
}

View File

@@ -23,7 +23,9 @@ public class SnapshotPostbackRequestInvalidFiles extends SnapshotPostbackRequest
public SnapshotPostbackRequestInvalidFiles(JsonArray errors) {
this(new ArrayList<InvalidFileError>());
for (JsonElement error : errors) {
this.errors.add(InvalidFileError.buildFromJsonError(error.getAsJsonObject()));
this.errors.add(InvalidFileError.buildFromJsonError(
error.getAsJsonObject()
));
}
}

View File

@@ -11,7 +11,8 @@ import java.util.List;
/**
* Created by Winston on 10/01/15.
*/
public class SnapshotPostbackRequestInvalidProject extends SnapshotPostbackRequest {
public class SnapshotPostbackRequestInvalidProject
extends SnapshotPostbackRequest {
private final List<String> errors;

View File

@@ -30,9 +30,13 @@ public abstract class InvalidFileError {
} else if (state.equals("disallowed")) {
return new InvalidFileErrorDisallowed(file);
} else if (state.equals("unclean_name")) {
return new InvalidFileErrorUnclean(file, error.get("cleanFile").getAsString());
return new InvalidFileErrorUnclean(
file, error.get("cleanFile").getAsString()
);
} else {
throw new IllegalArgumentException("bad invalid file state: " + state);
throw new IllegalArgumentException(
"bad invalid file state: " + state
);
}
}

View File

@@ -2,9 +2,9 @@ package uk.ac.ic.wlgitbridge.snapshot.servermock.server;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import uk.ac.ic.wlgitbridge.snapshot.servermock.exception.InvalidAPICallException;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponse;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.SnapshotResponseBuilder;
import uk.ac.ic.wlgitbridge.snapshot.servermock.exception.
InvalidAPICallException;
import uk.ac.ic.wlgitbridge.snapshot.servermock.response.*;
import uk.ac.ic.wlgitbridge.util.Log;
import javax.servlet.ServletException;
@@ -19,17 +19,30 @@ public class MockSnapshotRequestHandler extends AbstractHandler {
private final SnapshotResponseBuilder responseBuilder;
public MockSnapshotRequestHandler(SnapshotResponseBuilder responseBuilder) {
public MockSnapshotRequestHandler(
SnapshotResponseBuilder responseBuilder
) {
this.responseBuilder = responseBuilder;
}
@Override
public void handle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
public void handle(
String target,
final Request baseRequest,
HttpServletRequest request,
HttpServletResponse response
) throws IOException, ServletException {
boolean handled;
try {
final SnapshotResponse snapshotResponse = responseBuilder.buildWithTarget(target, baseRequest.getMethod());
final SnapshotResponse snapshotResponse
= responseBuilder.buildWithTarget(
target, baseRequest.getMethod()
);
response.getWriter().println(snapshotResponse.respond());
new PostbackThread(baseRequest.getReader(), snapshotResponse.postback()).startIfNotNull();
new PostbackThread(
baseRequest.getReader(),
snapshotResponse.postback()
).startIfNotNull();
handled = true;
} catch (InvalidAPICallException e) {
handled = false;

View File

@@ -19,7 +19,10 @@ public class PostbackThread extends Thread {
public PostbackThread(Reader reader, String postback) {
if (postback != null) {
url = new Gson().fromJson(reader, JsonObject.class).get("postbackUrl").getAsString();
url = new Gson().fromJson(
reader,
JsonObject.class
).get("postbackUrl").getAsString();
this.postback = postback;
}
}
@@ -27,7 +30,9 @@ public class PostbackThread extends Thread {
@Override
public void run() {
try {
new AsyncHttpClient().preparePost(url).setBody(postback).execute().get().getResponseBody();
new AsyncHttpClient().preparePost(
url
).setBody(postback).execute().get().getResponseBody();
} catch (IOException e) {
Log.warn(
"IOException on postback, url: " +

View File

@@ -25,12 +25,13 @@ public class SnapshotAPIState {
private Map<String, SnapshotPushResult> push;
private Map<String, SnapshotPostbackRequest> postback;
public SnapshotAPIState(Map<String, GetDocResult> getDoc,
Map<String, GetSavedVersResult> getSavedVers,
Map<String, Map<Integer, GetForVersionResult>> getForVers,
Map<String, SnapshotPushResult> push,
Map<String, SnapshotPostbackRequest> postback) {
public SnapshotAPIState(
Map<String, GetDocResult> getDoc,
Map<String, GetSavedVersResult> getSavedVers,
Map<String, Map<Integer, GetForVersionResult>> getForVers,
Map<String, SnapshotPushResult> push,
Map<String, SnapshotPostbackRequest> postback
) {
this.getDoc = getDoc;
this.getSavedVers = getSavedVers;
this.getForVers = getForVers;
@@ -39,19 +40,45 @@ public class SnapshotAPIState {
}
public SnapshotAPIState() {
getDoc = new HashMap<String, GetDocResult>();
getDoc.put("1826rqgsdb", new GetDocResult(null, 243, "2014-11-30T18:40:58Z", "jdleesmiller+1@gmail.com", "John+1"));
getDoc = new HashMap<>();
getDoc.put(
"1826rqgsdb",
new GetDocResult(
null,
243,
"2014-11-30T18:40:58Z",
"jdleesmiller+1@gmail.com",
"John+1"
)
);
getSavedVers = new HashMap<String, GetSavedVersResult>();
List<SnapshotInfo> savedVers = new LinkedList<SnapshotInfo>();
savedVers.add(new SnapshotInfo(243, "added more info on doc GET and error details", "jdleesmiller+1@gmail.com", "John+1", "2014-11-30T18:47:01Z"));
savedVers.add(new SnapshotInfo(185, "with more details on POST request", "jdleesmiller+1@gmail.com", "John+1", "2014-11-11T17:18:40Z"));
savedVers.add(new SnapshotInfo(175, "with updated PUT/POST request", "jdleesmiller+1@gmail.com", "John+1", "2014-11-09T23:09:13Z"));
savedVers.add(new SnapshotInfo(146, "added PUT format", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-07T15:11:35Z"));
savedVers.add(new SnapshotInfo(74, "with example output", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T18:09:41Z"));
savedVers.add(new SnapshotInfo(39, "with more files", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T18:02:19Z"));
savedVers.add(new SnapshotInfo(24, "first draft", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T17:56:58Z"));
savedVers.add(new SnapshotInfo(
243,
"added more info on doc GET and error details",
"jdleesmiller+1@gmail.com",
"John+1",
"2014-11-30T18:47:01Z"
));
savedVers.add(new SnapshotInfo(
185, "with more details on POST request", "jdleesmiller+1@gmail.com", "John+1", "2014-11-11T17:18:40Z"
));
savedVers.add(new SnapshotInfo(
175, "with updated PUT/POST request", "jdleesmiller+1@gmail.com", "John+1", "2014-11-09T23:09:13Z"
));
savedVers.add(new SnapshotInfo(
146, "added PUT format", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-07T15:11:35Z"
));
savedVers.add(new SnapshotInfo(
74, "with example output", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T18:09:41Z"
));
savedVers.add(new SnapshotInfo(
39, "with more files", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T18:02:19Z"
));
savedVers.add(new SnapshotInfo(
24, "first draft", "jdleesmiller@gmail.com", "John Lees-Miller", "2014-11-05T17:56:58Z"
));
getSavedVers.put("1826rqgsdb", new GetSavedVersResult(savedVers));
getForVers = new HashMap<String, Map<Integer, GetForVersionResult>>() {{
@@ -105,9 +132,33 @@ public class SnapshotAPIState {
}};
postback = new HashMap<String, SnapshotPostbackRequest>() {{
// put("1826rqgsdb", new SnapshotPostbackRequestInvalidFiles(Arrays.<InvalidFileError>asList(new InvalidFileErrorDefault("file1.invalid"), new InvalidFileErrorDisallowed("file2.exe"), new InvalidFileErrorUnclean("hello world.png", "hello_world.png"))));
// put(
// "1826rqgsdb",
// new SnapshotPostbackRequestInvalidFiles(
// Arrays.<InvalidFileError>asList(
// new InvalidFileErrorDefault(
// "file1.invalid"
// ),
// new InvalidFileErrorDisallowed(
// "file2.exe"
// ),
// new InvalidFileErrorUnclean(
// "hello world.png",
// "hello_world.png"
// )
// )
// )
// );
// put("1826rqgsdb", new SnapshotPostbackRequestOutOfDate());
put("1826rqgsdb", new SnapshotPostbackRequestInvalidProject(Arrays.asList("Your project is missing main.tex.", "Please name your main latex file main.tex.")));
put(
"1826rqgsdb",
new SnapshotPostbackRequestInvalidProject(
Arrays.asList(
"Your project is missing main.tex.",
"Please name your main latex file main.tex."
)
)
);
// put("1826rqgsdb", new SnapshotPostbackRequestError());
}};
}
@@ -120,7 +171,10 @@ public class SnapshotAPIState {
return getSavedVers.get(projectName);
}
public GetForVersionResult getStateForGetForVers(String projectName, int versionID) {
public GetForVersionResult getStateForGetForVers(
String projectName,
int versionID
) {
return getForVers.get(projectName).get(versionID);
}

View File

@@ -18,10 +18,7 @@ import uk.ac.ic.wlgitbridge.snapshot.servermock.response.push.postback.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Created by Winston on 11/01/15.
@@ -30,70 +27,127 @@ public class SnapshotAPIStateBuilder {
private final JsonArray projects;
private Map<String, GetDocResult> getDoc = new HashMap<String, GetDocResult>();
private Map<String, GetSavedVersResult> getSavedVers = new HashMap<String, GetSavedVersResult>();
private Map<String, Map<Integer, GetForVersionResult>> getForVers = new HashMap<String, Map<Integer, GetForVersionResult>>();
private Map<String, SnapshotPushResult> push = new HashMap<String, SnapshotPushResult>();
private Map<String, SnapshotPostbackRequest> postback = new HashMap<String, SnapshotPostbackRequest>();
private Map<String, GetDocResult> getDoc = new HashMap<>();
private Map<String, GetSavedVersResult> getSavedVers = new HashMap<>();
private Map<String, Map<Integer, GetForVersionResult>> getForVers
= new HashMap<>();
private Map<String, SnapshotPushResult> push
= new HashMap<>();
private Map<String, SnapshotPostbackRequest> postback
= new HashMap<>();
public SnapshotAPIStateBuilder(InputStream stream) {
projects = new Gson().fromJson(new InputStreamReader(stream), JsonArray.class);
projects = new Gson().fromJson(
new InputStreamReader(stream), JsonArray.class
);
}
public SnapshotAPIState build() {
for (JsonElement project : projects) {
addProject(project.getAsJsonObject());
}
return new SnapshotAPIState(getDoc, getSavedVers, getForVers, push, postback);
return new SnapshotAPIState(
getDoc,
getSavedVers,
getForVers,
push,
postback
);
}
private void addProject(JsonObject project) {
String projectName = project.get("project").getAsString();
addGetDocForProject(projectName, project.get("getDoc").getAsJsonObject());
addGetSavedVersForProject(projectName, project.get("getSavedVers").getAsJsonArray());
addGetForVersForProject(projectName, project.get("getForVers").getAsJsonArray());
addPushForProject(projectName, project.get("push").getAsString());
addPostbackForProject(projectName, project.get("postback").getAsJsonObject());
addGetDocForProject(
projectName,
project.get("getDoc").getAsJsonObject()
);
addGetSavedVersForProject(
projectName,
project.get("getSavedVers").getAsJsonArray()
);
addGetForVersForProject(
projectName,
project.get("getForVers").getAsJsonArray()
);
addPushForProject(
projectName,
project.get("push").getAsString()
);
addPostbackForProject(
projectName,
project.get("postback").getAsJsonObject()
);
}
private void addGetDocForProject(String projectName, JsonObject jsonGetDoc) {
getDoc.put(projectName,
new GetDocResult(jsonGetDoc.get("error"),
jsonGetDoc.get("versionID").getAsInt(),
jsonGetDoc.get("createdAt").getAsString(),
jsonGetDoc.get("email").getAsString(),
jsonGetDoc.get("name").getAsString()));
private void addGetDocForProject(
String projectName,
JsonObject jsonGetDoc
) {
getDoc.put(
projectName,
new GetDocResult(
jsonGetDoc.get("error"),
jsonGetDoc.get("versionID").getAsInt(),
jsonGetDoc.get("createdAt").getAsString(),
jsonGetDoc.get("email").getAsString(),
jsonGetDoc.get("name").getAsString()
)
);
}
private void addGetSavedVersForProject(String projectName, JsonArray jsonGetSavedVers) {
List<SnapshotInfo> savedVers = new LinkedList<SnapshotInfo>();
private void addGetSavedVersForProject(
String projectName,
JsonArray jsonGetSavedVers
) {
List<SnapshotInfo> savedVers = new ArrayList<>();
for (JsonElement ver : jsonGetSavedVers) {
savedVers.add(getSnapshotInfo(ver.getAsJsonObject()));
}
getSavedVers.put(projectName, new GetSavedVersResult(savedVers));
}
private SnapshotInfo getSnapshotInfo(JsonObject jsonSnapshotInfo) {
return new SnapshotInfo(jsonSnapshotInfo.get("versionID").getAsInt(),
jsonSnapshotInfo.get("comment").getAsString(),
jsonSnapshotInfo.get("email").getAsString(),
jsonSnapshotInfo.get("name").getAsString(),
jsonSnapshotInfo.get("createdAt").getAsString());
private SnapshotInfo getSnapshotInfo(
JsonObject jsonSnapshotInfo
) {
return new SnapshotInfo(
jsonSnapshotInfo.get("versionID").getAsInt(),
jsonSnapshotInfo.get("comment").getAsString(),
jsonSnapshotInfo.get("email").getAsString(),
jsonSnapshotInfo.get("name").getAsString(),
jsonSnapshotInfo.get("createdAt").getAsString()
);
}
private void addGetForVersForProject(String projectName, JsonArray jsonGetForVers) {
Map<Integer, GetForVersionResult> forVers = new HashMap<Integer, GetForVersionResult>();
private void addGetForVersForProject(
String projectName,
JsonArray jsonGetForVers
) {
Map<Integer, GetForVersionResult> forVers = new HashMap<>();
for (JsonElement forVer : jsonGetForVers) {
JsonObject forVerObj = forVer.getAsJsonObject();
forVers.put(forVerObj.get("versionID").getAsInt(),
new GetForVersionResult(new SnapshotData(getSrcs(forVerObj.get("srcs").getAsJsonArray()),
getAtts(forVerObj.get("atts").getAsJsonArray()))));
forVers.put(
forVerObj.get("versionID").getAsInt(),
new GetForVersionResult(
new SnapshotData(
getSrcs(
forVerObj.get(
"srcs"
).getAsJsonArray()
),
getAtts(
forVerObj.get(
"atts"
).getAsJsonArray()
)
)
)
);
}
getForVers.put(projectName, forVers);
}
private List<SnapshotFile> getSrcs(JsonArray jsonSrcs) {
List<SnapshotFile> srcs = new LinkedList<SnapshotFile>();
List<SnapshotFile> srcs = new ArrayList<>();
for (JsonElement src : jsonSrcs) {
srcs.add(getSrc(src.getAsJsonObject()));
}
@@ -106,7 +160,7 @@ public class SnapshotAPIStateBuilder {
}
private List<SnapshotAttachment> getAtts(JsonArray jsonAtts) {
List<SnapshotAttachment> atts = new LinkedList<SnapshotAttachment>();
List<SnapshotAttachment> atts = new LinkedList<>();
for (JsonElement att : jsonAtts) {
atts.add(getAtt(att.getAsJsonObject()));
}
@@ -130,17 +184,26 @@ public class SnapshotAPIStateBuilder {
push.put(projectName, p);
}
private void addPostbackForProject(String projectName, JsonObject jsonPostback) {
private void addPostbackForProject(
String projectName,
JsonObject jsonPostback
) {
SnapshotPostbackRequest p;
String type = jsonPostback.get("type").getAsString();
if (type.equals("success")) {
p = new SnapshotPostbackRequestSuccess(jsonPostback.get("versionID").getAsInt());
p = new SnapshotPostbackRequestSuccess(
jsonPostback.get("versionID").getAsInt()
);
} else if (type.equals("outOfDate")) {
p = new SnapshotPostbackRequestOutOfDate();
} else if (type.equals("invalidFiles")) {
p = new SnapshotPostbackRequestInvalidFiles(jsonPostback.get("errors").getAsJsonArray());
p = new SnapshotPostbackRequestInvalidFiles(
jsonPostback.get("errors").getAsJsonArray()
);
} else if (type.equals("invalidProject")) {
p = new SnapshotPostbackRequestInvalidProject(jsonPostback.get("errors").getAsJsonArray());
p = new SnapshotPostbackRequestInvalidProject(
jsonPostback.get("errors").getAsJsonArray()
);
} else if (type.equals("error")) {
p = new SnapshotPostbackRequestError();
} else {

View File

@@ -1,10 +0,0 @@
package uk.ac.ic.wlgitbridge.snapshot.servermock.state;
/**
* Created by Winston on 11/01/15.
*/
public class SnapshotAPIStateManager {
}

View File

@@ -21,8 +21,16 @@ public class FileUtil {
public static boolean currentCommitsAreEqual(Path dir1, Path dir2) {
try {
RevCommit commit1 = new Git(new FileRepositoryBuilder().setWorkTree(dir1.toFile().getAbsoluteFile()).build()).log().call().iterator().next();
RevCommit commit2 = new Git(new FileRepositoryBuilder().setWorkTree(dir2.toFile().getAbsoluteFile()).build()).log().call().iterator().next();
RevCommit commit1 = new Git(
new FileRepositoryBuilder().setWorkTree(
dir1.toFile().getAbsoluteFile()
).build()
).log().call().iterator().next();
RevCommit commit2 = new Git(
new FileRepositoryBuilder().setWorkTree(
dir2.toFile().getAbsoluteFile()
).build()
).log().call().iterator().next();
return commit1.equals(commit2);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -34,22 +42,42 @@ public class FileUtil {
}
public static boolean gitDirectoriesAreEqual(Path dir1, Path dir2) {
Set<String> dir1Contents = getAllRecursivelyInDirectoryApartFrom(dir1, dir1.resolve(".git"));
Set<String> dir2Contents = getAllRecursivelyInDirectoryApartFrom(dir2, dir2.resolve(".git"));
Set<String> dir1Contents = getAllRecursivelyInDirectoryApartFrom(
dir1,
dir1.resolve(".git")
);
Set<String> dir2Contents = getAllRecursivelyInDirectoryApartFrom(
dir2,
dir2.resolve(".git")
);
boolean filesEqual = dir1Contents.equals(dir2Contents);
if (!filesEqual) {
System.out.println("Not equal: (" + dir1Contents + ", " + dir2Contents + ")");
System.out.println(
"Not equal: ("
+ dir1Contents
+ ", "
+ dir2Contents
+ ")"
);
System.out.println(dir1 + ": " + dir1Contents);
System.out.println(dir2 + ": " + dir2Contents);
}
return filesEqual && directoryContentsEqual(dir1Contents, dir1, dir2);
}
static boolean directoryContentsEqual(Set<String> dirContents, Path dir1, Path dir2) {
static boolean directoryContentsEqual(
Set<String> dirContents,
Path dir1,
Path dir2
) {
for (String file : dirContents) {
Path path1 = dir1.resolve(file);
Path path2 = dir2.resolve(file);
if (!path1.toFile().isDirectory() && !path2.toFile().isDirectory() && !fileContentsEqual(path1, path2)) {
if (
!path1.toFile().isDirectory()
&& !path2.toFile().isDirectory()
&& !fileContentsEqual(path1, path2)
) {
return false;
}
}
@@ -62,7 +90,9 @@ public class FileUtil {
byte[] secondContents = Files.readAllBytes(second);
boolean equals = Arrays.equals(firstContents, secondContents);
if (!equals) {
System.out.println("Not equal: (" + first + ", " + second + ")");
System.out.println(
"Not equal: (" + first + ", " + second + ")"
);
System.out.println(first + ": " + new String(firstContents));
System.out.println(second + ": " + new String(secondContents));
}
@@ -72,22 +102,37 @@ public class FileUtil {
}
}
public static Set<String> getAllRecursivelyInDirectoryApartFrom(Path dir, Path excluded) {
public static Set<String> getAllRecursivelyInDirectoryApartFrom(
Path dir,
Path excluded
) {
return getAllRecursivelyInDirectoryApartFrom(dir, excluded, true);
}
public static Set<String> getOnlyFilesRecursivelyInDirectoryApartFrom(Path dir, Path excluded) {
public static Set<String> getOnlyFilesRecursivelyInDirectoryApartFrom(
Path dir,
Path excluded
) {
return getAllRecursivelyInDirectoryApartFrom(dir, excluded, false);
}
private static Set<String> getAllRecursivelyInDirectoryApartFrom(Path dir, Path excluded, boolean directories) {
private static Set<String> getAllRecursivelyInDirectoryApartFrom(
Path dir,
Path excluded,
boolean directories
) {
if (!dir.toFile().isDirectory()) {
throw new IllegalArgumentException("need a directory");
}
return getAllFilesRecursively(dir, dir, excluded, directories);
}
static Set<String> getAllFilesRecursively(Path baseDir, Path dir, Path excluded, boolean directories) {
static Set<String> getAllFilesRecursively(
Path baseDir,
Path dir,
Path excluded,
boolean directories
) {
Set<String> files = new HashSet<String>();
for (File file : dir.toFile().listFiles()) {
if (!file.equals(excluded.toFile())) {
@@ -96,7 +141,12 @@ public class FileUtil {
files.add(baseDir.relativize(file.toPath()).toString());
}
if (isDirectory) {
files.addAll(getAllFilesRecursively(baseDir, file.toPath(), excluded, directories));
files.addAll(getAllFilesRecursively(
baseDir,
file.toPath(),
excluded,
directories
));
}
}
}

View File

@@ -59,7 +59,10 @@ public class Files {
return renamed;
}
private static boolean uncheckedContentsAreEqual(File f0, File f1) throws IOException {
private static boolean uncheckedContentsAreEqual(
File f0,
File f1
) throws IOException {
if (f0.equals(f1)) {
return true;
}

View File

@@ -22,7 +22,8 @@ public class Instance {
public static final JsonFactory jsonFactory = new GsonFactory();
public static final Gson prettyGson =
new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
new GsonBuilder(
).setPrettyPrinting().disableHtmlEscaping().create();
public static final Gson gson = new Gson();

View File

@@ -17,7 +17,8 @@ public class Util {
private static String HOSTNAME;
private static int PORT;
private static String POSTBACK_URL;
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS");
private static final DateFormat dateFormat
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS");
public static String entries(int entries) {
if (entries == 1) {
@@ -57,9 +58,11 @@ public class Util {
return result;
}
public static String getContentsOfReader(BufferedReader reader) throws IOException {
public static String getContentsOfReader(
BufferedReader reader
) throws IOException {
StringBuilder sb = new StringBuilder();
for (String line; (line = reader.readLine()) != null; ) {
for (String line; (line = reader.readLine()) != null;) {
sb.append(line);
}
return sb.toString();
@@ -102,9 +105,12 @@ public class Util {
}
}
public static void deleteInDirectoryApartFrom(File directory, String... apartFrom) {
public static void deleteInDirectoryApartFrom(
File directory,
String... apartFrom
) {
if (directory != null) {
Set<String> excluded = new HashSet<String>(Arrays.asList(apartFrom));
Set<String> excluded = new HashSet<>(Arrays.asList(apartFrom));
File [] files = directory.listFiles();
if (files != null) {
for (File file : files) {
@@ -120,9 +126,15 @@ public class Util {
}
}
public static List<String> linesFromStream(InputStream stream, int skip, String trimSuffix) throws IOException {
List<String> lines = new ArrayList<String>();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
public static List<String> linesFromStream(
InputStream stream,
int skip,
String trimSuffix
) throws IOException {
List<String> lines = new ArrayList<>();
BufferedReader reader = new BufferedReader(
new InputStreamReader(stream)
);
String line;
for (int i = 0; i < skip; i++) {
reader.readLine();
@@ -139,8 +151,13 @@ public class Util {
return lines;
}
public static String fromStream(InputStream stream, int skip) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
public static String fromStream(
InputStream stream,
int skip
) throws IOException {
BufferedReader reader = new BufferedReader(
new InputStreamReader(stream)
);
StringBuilder out = new StringBuilder();
String newLine = System.getProperty("line.separator");
String line;
@@ -182,7 +199,6 @@ public class Util {
} else {
code = codeElement.getAsString();
}
return code;
}

View File

@@ -323,11 +323,11 @@ public class WLGitBridgeIntegrationTest {
public void canPushFilesSuccessfully() throws IOException, GitAPIException, InterruptedException {
MockSnapshotServer server = new MockSnapshotServer(3866, getResource("/canPushFilesSuccessfully").toFile());
server.start();
server.setState(states.get("canPushFilesSuccessfully").get("state"));
GitBridgeApp wlgb = new GitBridgeApp(new String[] {
makeConfigFile(33866, 3866)
});
wlgb.run();
server.setState(states.get("canPushFilesSuccessfully").get("state"));
File dir = folder.newFolder();
File testprojDir = cloneRepository("testproj", 33866, dir);
assertTrue(FileUtil.gitDirectoriesAreEqual(getResource("/canPushFilesSuccessfully/state/testproj"), testprojDir.toPath()));