diff options
author | James Moger <james.moger@gitblit.com> | 2013-11-21 17:26:04 -0500 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2013-11-29 11:05:51 -0500 |
commit | 269c5043ab8f66f67d5719ac5149a436ca1baa2b (patch) | |
tree | ec29b9b53da9ced68b3533380dfc0370fb9c496e /src | |
parent | a1f27e2fac7b38b87645bd53b7e023484c796f1c (diff) | |
download | gitblit-269c5043ab8f66f67d5719ac5149a436ca1baa2b.tar.gz gitblit-269c5043ab8f66f67d5719ac5149a436ca1baa2b.zip |
Extract Federation, Gitblit and Services manager from GitBlit singleton
Change-Id: I2b2f361a868c8eedf4b6df5939e7dfac2d5f92a9
Diffstat (limited to 'src')
19 files changed, 1820 insertions, 1611 deletions
diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java index 8cd9c8b5..0cbb739a 100644 --- a/src/main/java/com/gitblit/DaggerModule.java +++ b/src/main/java/com/gitblit/DaggerModule.java @@ -20,18 +20,22 @@ import javax.inject.Singleton; import org.apache.wicket.protocol.http.WebApplication; import com.gitblit.git.GitServlet; +import com.gitblit.manager.FederationManager; +import com.gitblit.manager.GitblitManager; import com.gitblit.manager.IFederationManager; import com.gitblit.manager.IGitblitManager; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; import com.gitblit.manager.ISessionManager; import com.gitblit.manager.IUserManager; import com.gitblit.manager.NotificationManager; import com.gitblit.manager.ProjectManager; import com.gitblit.manager.RepositoryManager; import com.gitblit.manager.RuntimeManager; +import com.gitblit.manager.ServicesManager; import com.gitblit.manager.SessionManager; import com.gitblit.manager.UserManager; import com.gitblit.wicket.GitBlitWebApp; @@ -47,6 +51,7 @@ import dagger.Provides; * */ @Module( + library = true, injects = { IStoredSettings.class, @@ -59,6 +64,7 @@ import dagger.Provides; IProjectManager.class, IGitblitManager.class, IFederationManager.class, + IServicesManager.class, // the monolithic manager Gitblit.class, @@ -85,13 +91,6 @@ import dagger.Provides; ) public class DaggerModule { - final GitBlit gitblit; - - // HACK but necessary for now - public DaggerModule(GitBlit gitblit) { - this.gitblit = gitblit; - } - @Provides @Singleton IStoredSettings provideSettings() { return new FileSettings(); } @@ -137,12 +136,28 @@ public class DaggerModule { repositoryManager); } - @Provides @Singleton IGitblitManager provideGitblitManager() { - return gitblit; + @Provides @Singleton IFederationManager provideFederationManager( + IRuntimeManager runtimeManager, + INotificationManager notificationManager, + IUserManager userManager, + IRepositoryManager repositoryManager) { + + return new FederationManager( + runtimeManager, + notificationManager, + userManager, + repositoryManager); } - @Provides @Singleton IFederationManager provideFederationManager() { - return gitblit; + @Provides @Singleton IGitblitManager provideGitblitManager( + IRuntimeManager runtimeManager, + IUserManager userManager, + IRepositoryManager repositoryManager) { + + return new GitblitManager( + runtimeManager, + userManager, + repositoryManager); } @Provides @Singleton Gitblit provideGitblit( @@ -162,8 +177,12 @@ public class DaggerModule { sessionManager, repositoryManager, projectManager, - federationManager, - gitblitManager); + gitblitManager, + federationManager); + } + + @Provides @Singleton IServicesManager provideServicesManager(Gitblit gitblit) { + return new ServicesManager(gitblit); } @Provides @Singleton WebApplication provideWebApplication( diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java index 862b64c1..f32fa0f2 100644 --- a/src/main/java/com/gitblit/FederationClient.java +++ b/src/main/java/com/gitblit/FederationClient.java @@ -23,6 +23,11 @@ import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
+import com.gitblit.manager.FederationManager;
+import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
import com.gitblit.models.FederationModel;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.StringUtils;
@@ -53,7 +58,7 @@ public class FederationClient { }
File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
- IStoredSettings settings = new FileSettings(regFile.getAbsolutePath());
+ FileSettings settings = new FileSettings(regFile.getAbsolutePath());
List<FederationModel> registrations = new ArrayList<FederationModel>();
if (StringUtils.isEmpty(params.url)) {
registrations.addAll(FederationUtils.getFederationRegistrations(settings));
@@ -83,14 +88,23 @@ public class FederationClient { }
// configure the Gitblit singleton for minimal, non-server operation
- GitBlit gitblit = new GitBlit(settings, baseFolder);
- gitblit.beforeServletInjection(null); // XXX broken
- FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
- executor.run();
- if (!params.isDaemon) {
- System.out.println("Finished.");
- System.exit(0);
- }
+ RuntimeManager runtime = new RuntimeManager(settings);
+ runtime.setBaseFolder(baseFolder);
+ NotificationManager notifications = new NotificationManager(settings).start();
+ UserManager users = new UserManager(runtime).start();
+ RepositoryManager repositories = new RepositoryManager(runtime, users).start();
+ FederationManager federation = new FederationManager(runtime, notifications, users, repositories).start();
+
+ FederationPullExecutor puller = new FederationPullExecutor(federation.getFederationRegistrations()) {
+ @Override
+ public void reschedule(FederationModel registration) {
+ // NOOP
+ }
+ };
+ puller.run();
+
+ System.out.println("Finished.");
+ System.exit(0);
}
private static void usage(JCommander jc, ParameterException t) {
@@ -116,9 +130,6 @@ public class FederationClient { @Parameter(names = { "--registrations" }, description = "Gitblit Federation Registrations File", required = false)
public String registrationsFile = "${baseFolder}/federation.properties";
- @Parameter(names = { "--daemon" }, description = "Runs in daemon mode to schedule and pull repositories", required = false)
- public boolean isDaemon;
-
@Parameter(names = { "--url" }, description = "URL of Gitblit instance to mirror from", required = false)
public String url;
diff --git a/src/main/java/com/gitblit/FederationPullExecutor.java b/src/main/java/com/gitblit/FederationPullExecutor.java index e9a604da..bbe73cfa 100644 --- a/src/main/java/com/gitblit/FederationPullExecutor.java +++ b/src/main/java/com/gitblit/FederationPullExecutor.java @@ -1,533 +1,486 @@ -/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.CredentialsProvider;
-import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.FederationPullStatus;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.GitBlitException.ForbiddenException;
-import com.gitblit.manager.IGitblitManager;
-import com.gitblit.manager.INotificationManager;
-import com.gitblit.manager.IRepositoryManager;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.manager.IUserManager;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.JGitUtils.CloneResult;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-
-/**
- * FederationPullExecutor pulls repository updates and, optionally, user
- * accounts and server settings from registered Gitblit instances.
- */
-public class FederationPullExecutor implements Runnable {
-
- private final Logger logger = LoggerFactory.getLogger(FederationPullExecutor.class);
-
- private final List<FederationModel> registrations;
-
- private final boolean isDaemon;
-
- /**
- * Constructor for specifying a single federation registration. This
- * constructor is used to schedule the next pull execution.
- *
- * @param registration
- */
- private FederationPullExecutor(FederationModel registration) {
- this(Arrays.asList(registration), true);
- }
-
- /**
- * Constructor to specify a group of federation registrations. This is
- * normally used at startup to pull and then schedule the next update based
- * on each registrations frequency setting.
- *
- * @param registrations
- * @param isDaemon
- * if true, registrations are rescheduled in perpetuity. if
- * false, the federation pull operation is executed once.
- */
- public FederationPullExecutor(List<FederationModel> registrations, boolean isDaemon) {
- this.registrations = registrations;
- this.isDaemon = isDaemon;
- }
-
- /**
- * Run method for this pull executor.
- */
- @Override
- public void run() {
- for (FederationModel registration : registrations) {
- FederationPullStatus was = registration.getLowestStatus();
- try {
- Date now = new Date(System.currentTimeMillis());
- pull(registration);
- sendStatusAcknowledgment(registration);
- registration.lastPull = now;
- FederationPullStatus is = registration.getLowestStatus();
- if (is.ordinal() < was.ordinal()) {
- // the status for this registration has downgraded
- logger.warn("Federation pull status of {0} is now {1}", registration.name,
- is.name());
- if (registration.notifyOnError) {
- String message = "Federation pull of " + registration.name + " @ "
- + registration.url + " is now at " + is.name();
- INotificationManager mailManager = GitBlit.getManager(INotificationManager.class);
- mailManager
- .sendMailToAdministrators(
- "Pull Status of " + registration.name + " is " + is.name(),
- message);
- }
- }
- } catch (Throwable t) {
- logger.error(MessageFormat.format(
- "Failed to pull from federated gitblit ({0} @ {1})", registration.name,
- registration.url), t);
- } finally {
- if (isDaemon) {
- schedule(registration);
- }
- }
- }
- }
-
- /**
- * Mirrors a repository and, optionally, the server's users, and/or
- * configuration settings from a origin Gitblit instance.
- *
- * @param registration
- * @throws Exception
- */
- private void pull(FederationModel registration) throws Exception {
- Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
- true);
- String registrationFolder = registration.folder.toLowerCase().trim();
- // confirm valid characters in server alias
- Character c = StringUtils.findInvalidCharacter(registrationFolder);
- if (c != null) {
- logger.error(MessageFormat
- .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
- c, registrationFolder, registration.name));
- return;
- }
- IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class);
- File repositoriesFolder = repositoryManager.getRepositoriesFolder();
- File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
- registrationFolderFile.mkdirs();
-
- // Clone/Pull the repository
- for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
- String cloneUrl = entry.getKey();
- RepositoryModel repository = entry.getValue();
- if (!repository.hasCommits) {
- logger.warn(MessageFormat.format(
- "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
- repository.name, registration.name, registration.url));
- registration.updateStatus(repository, FederationPullStatus.SKIPPED);
- continue;
- }
-
- // Determine local repository name
- String repositoryName;
- if (StringUtils.isEmpty(registrationFolder)) {
- repositoryName = repository.name;
- } else {
- repositoryName = registrationFolder + "/" + repository.name;
- }
-
- if (registration.bare) {
- // bare repository, ensure .git suffix
- if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
- repositoryName += DOT_GIT_EXT;
- }
- } else {
- // normal repository, strip .git suffix
- if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
- repositoryName = repositoryName.substring(0,
- repositoryName.indexOf(DOT_GIT_EXT));
- }
- }
-
- // confirm that the origin of any pre-existing repository matches
- // the clone url
- String fetchHead = null;
- Repository existingRepository = repositoryManager.getRepository(repositoryName);
-
- if (existingRepository == null && repositoryManager.isCollectingGarbage(repositoryName)) {
- logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName));
- continue;
- }
-
- if (existingRepository != null) {
- StoredConfig config = existingRepository.getConfig();
- config.load();
- String origin = config.getString("remote", "origin", "url");
- RevCommit commit = JGitUtils.getCommit(existingRepository,
- org.eclipse.jgit.lib.Constants.FETCH_HEAD);
- if (commit != null) {
- fetchHead = commit.getName();
- }
- existingRepository.close();
- if (!origin.startsWith(registration.url)) {
- logger.warn(MessageFormat
- .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
- repository.name, registration.name, registration.url));
- registration.updateStatus(repository, FederationPullStatus.SKIPPED);
- continue;
- }
- }
-
- // clone/pull this repository
- CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
- Constants.FEDERATION_USER, registration.token);
- logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
- repository.name, registration.name, registration.url));
-
- CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
- cloneUrl, registration.bare, credentials);
- Repository r = repositoryManager.getRepository(repositoryName);
- RepositoryModel rm = repositoryManager.getRepositoryModel(repositoryName);
- repository.isFrozen = registration.mirror;
- if (result.createdRepository) {
- // default local settings
- repository.federationStrategy = FederationStrategy.EXCLUDE;
- repository.isFrozen = registration.mirror;
- repository.showRemoteBranches = !registration.mirror;
- logger.info(MessageFormat.format(" cloning {0}", repository.name));
- registration.updateStatus(repository, FederationPullStatus.MIRRORED);
- } else {
- // fetch and update
- boolean fetched = false;
- RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD);
- String newFetchHead = commit.getName();
- fetched = fetchHead == null || !fetchHead.equals(newFetchHead);
-
- if (registration.mirror) {
- // mirror
- if (fetched) {
- // update local branches to match the remote tracking branches
- for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) {
- if (ref.displayName.startsWith("origin/")) {
- String branch = org.eclipse.jgit.lib.Constants.R_HEADS
- + ref.displayName.substring(ref.displayName.indexOf('/') + 1);
- String hash = ref.getReferencedObjectId().getName();
-
- JGitUtils.setBranchRef(r, branch, hash);
- logger.info(MessageFormat.format(" resetting {0} of {1} to {2}", branch,
- repository.name, hash));
- }
- }
-
- String newHead;
- if (StringUtils.isEmpty(repository.HEAD)) {
- newHead = newFetchHead;
- } else {
- newHead = repository.HEAD;
- }
- JGitUtils.setHEADtoRef(r, newHead);
- logger.info(MessageFormat.format(" resetting HEAD of {0} to {1}",
- repository.name, newHead));
- registration.updateStatus(repository, FederationPullStatus.MIRRORED);
- } else {
- // indicate no commits pulled
- registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
- }
- } else {
- // non-mirror
- if (fetched) {
- // indicate commits pulled to origin/master
- registration.updateStatus(repository, FederationPullStatus.PULLED);
- } else {
- // indicate no commits pulled
- registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
- }
- }
-
- // preserve local settings
- repository.isFrozen = rm.isFrozen;
- repository.federationStrategy = rm.federationStrategy;
-
- // merge federation sets
- Set<String> federationSets = new HashSet<String>();
- if (rm.federationSets != null) {
- federationSets.addAll(rm.federationSets);
- }
- if (repository.federationSets != null) {
- federationSets.addAll(repository.federationSets);
- }
- repository.federationSets = new ArrayList<String>(federationSets);
-
- // merge indexed branches
- Set<String> indexedBranches = new HashSet<String>();
- if (rm.indexedBranches != null) {
- indexedBranches.addAll(rm.indexedBranches);
- }
- if (repository.indexedBranches != null) {
- indexedBranches.addAll(repository.indexedBranches);
- }
- repository.indexedBranches = new ArrayList<String>(indexedBranches);
-
- }
- // only repositories that are actually _cloned_ from the origin
- // Gitblit repository are marked as federated. If the origin
- // is from somewhere else, these repositories are not considered
- // "federated" repositories.
- repository.isFederated = cloneUrl.startsWith(registration.url);
-
- repositoryManager.updateConfiguration(r, repository);
- r.close();
- }
-
- IUserManager userManager = GitBlit.getManager(IUserManager.class);
- IGitblitManager gitblitManager = GitBlit.getManager(IGitblitManager.class);
- IUserService userService = null;
-
- try {
- // Pull USERS
- // TeamModels are automatically pulled because they are contained
- // within the UserModel. The UserService creates unknown teams
- // and updates existing teams.
- Collection<UserModel> users = FederationUtils.getUsers(registration);
- if (users != null && users.size() > 0) {
- File realmFile = new File(registrationFolderFile, registration.name + "_users.conf");
- realmFile.delete();
- userService = new ConfigUserService(realmFile);
- for (UserModel user : users) {
- userService.updateUserModel(user.username, user);
-
- // merge the origin permissions and origin accounts into
- // the user accounts of this Gitblit instance
- if (registration.mergeAccounts) {
- // reparent all repository permissions if the local
- // repositories are stored within subfolders
- if (!StringUtils.isEmpty(registrationFolder)) {
- if (user.permissions != null) {
- // pulling from >= 1.2 version
- Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
- user.permissions.clear();
- for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
- user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
- }
- } else {
- // pulling from <= 1.1 version
- List<String> permissions = new ArrayList<String>(user.repositories);
- user.repositories.clear();
- for (String permission : permissions) {
- user.addRepositoryPermission(registrationFolder + "/" + permission);
- }
- }
- }
-
- // insert new user or update local user
- UserModel localUser = userManager.getUserModel(user.username);
- if (localUser == null) {
- // create new local user
- gitblitManager.updateUserModel(user.username, user, true);
- } else {
- // update repository permissions of local user
- if (user.permissions != null) {
- // pulling from >= 1.2 version
- Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
- for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
- localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
- }
- } else {
- // pulling from <= 1.1 version
- for (String repository : user.repositories) {
- localUser.addRepositoryPermission(repository);
- }
- }
- localUser.password = user.password;
- localUser.canAdmin = user.canAdmin;
- gitblitManager.updateUserModel(localUser.username, localUser, false);
- }
-
- for (String teamname : userManager.getAllTeamNames()) {
- TeamModel team = userManager.getTeamModel(teamname);
- if (user.isTeamMember(teamname) && !team.hasUser(user.username)) {
- // new team member
- team.addUser(user.username);
- userManager.updateTeamModel(teamname, team);
- } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) {
- // remove team member
- team.removeUser(user.username);
- userManager.updateTeamModel(teamname, team);
- }
-
- // update team repositories
- TeamModel remoteTeam = user.getTeam(teamname);
- if (remoteTeam != null) {
- if (remoteTeam.permissions != null) {
- // pulling from >= 1.2
- for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
- team.setRepositoryPermission(entry.getKey(), entry.getValue());
- }
- userManager.updateTeamModel(teamname, team);
- } else if(!ArrayUtils.isEmpty(remoteTeam.repositories)) {
- // pulling from <= 1.1
- team.addRepositoryPermissions(remoteTeam.repositories);
- userManager.updateTeamModel(teamname, team);
- }
- }
- }
- }
- }
- }
- } catch (ForbiddenException e) {
- // ignore forbidden exceptions
- } catch (IOException e) {
- logger.warn(MessageFormat.format(
- "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
- registration.name, registration.url), e);
- }
-
- try {
- // Pull TEAMS
- // We explicitly pull these even though they are embedded in
- // UserModels because it is possible to use teams to specify
- // mailing lists or push scripts without specifying users.
- if (userService != null) {
- Collection<TeamModel> teams = FederationUtils.getTeams(registration);
- if (teams != null && teams.size() > 0) {
- for (TeamModel team : teams) {
- userService.updateTeamModel(team);
- }
- }
- }
- } catch (ForbiddenException e) {
- // ignore forbidden exceptions
- } catch (IOException e) {
- logger.warn(MessageFormat.format(
- "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})",
- registration.name, registration.url), e);
- }
-
- try {
- // Pull SETTINGS
- Map<String, String> settings = FederationUtils.getSettings(registration);
- if (settings != null && settings.size() > 0) {
- Properties properties = new Properties();
- properties.putAll(settings);
- FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
- registration.name + "_" + Constants.PROPERTIES_FILE));
- properties.store(os, null);
- os.close();
- }
- } catch (ForbiddenException e) {
- // ignore forbidden exceptions
- } catch (IOException e) {
- logger.warn(MessageFormat.format(
- "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
- registration.name, registration.url), e);
- }
-
- try {
- // Pull SCRIPTS
- Map<String, String> scripts = FederationUtils.getScripts(registration);
- if (scripts != null && scripts.size() > 0) {
- for (Map.Entry<String, String> script : scripts.entrySet()) {
- String scriptName = script.getKey();
- if (scriptName.endsWith(".groovy")) {
- scriptName = scriptName.substring(0, scriptName.indexOf(".groovy"));
- }
- File file = new File(registrationFolderFile, registration.name + "_"
- + scriptName + ".groovy");
- FileUtils.writeContent(file, script.getValue());
- }
- }
- } catch (ForbiddenException e) {
- // ignore forbidden exceptions
- } catch (IOException e) {
- logger.warn(MessageFormat.format(
- "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})",
- registration.name, registration.url), e);
- }
- }
-
- /**
- * Sends a status acknowledgment to the origin Gitblit instance. This
- * includes the results of the federated pull.
- *
- * @param registration
- * @throws Exception
- */
- private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
- if (!registration.sendStatus) {
- // skip status acknowledgment
- return;
- }
- InetAddress addr = InetAddress.getLocalHost();
- IStoredSettings settings = GitBlit.getManager(IRuntimeManager.class).getSettings();
- String federationName = settings.getString(Keys.federation.name, null);
- if (StringUtils.isEmpty(federationName)) {
- federationName = addr.getHostName();
- }
- FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
- logger.info(MessageFormat.format("Pull status sent to {0}", registration.url));
- }
-
- /**
- * Schedules the next check of the federated Gitblit instance.
- *
- * @param registration
- */
- private void schedule(FederationModel registration) {
- // schedule the next pull
- int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency);
- registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
- GitBlit.self().executor()
- .schedule(new FederationPullExecutor(registration), mins, TimeUnit.MINUTES);
- logger.info(MessageFormat.format(
- "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
- registration.name, registration.url, registration.nextPull));
- }
-}
+package com.gitblit; + +import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.FederationPullStatus; +import com.gitblit.Constants.FederationStrategy; +import com.gitblit.GitBlitException.ForbiddenException; +import com.gitblit.models.FederationModel; +import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.FederationUtils; +import com.gitblit.utils.FileUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.JGitUtils.CloneResult; +import com.gitblit.utils.StringUtils; + +public abstract class FederationPullExecutor implements Runnable { + + Logger logger = LoggerFactory.getLogger(getClass()); + + Gitblit gitblit; + + private final List<FederationModel> registrations; + + /** + * Constructor for specifying a single federation registration. This + * constructor is used to schedule the next pull execution. + * + * @param provider + * @param registration + */ + public FederationPullExecutor(FederationModel registration) { + this(Arrays.asList(registration)); + } + + /** + * Constructor to specify a group of federation registrations. This is + * normally used at startup to pull and then schedule the next update based + * on each registrations frequency setting. + * + * @param provider + * @param registrations + * @param isDaemon + * if true, registrations are rescheduled in perpetuity. if + * false, the federation pull operation is executed once. + */ + public FederationPullExecutor(List<FederationModel> registrations) { + this.registrations = registrations; + } + + public abstract void reschedule(FederationModel registration); + + /** + * Run method for this pull executor. + */ + @Override + public void run() { + for (FederationModel registration : registrations) { + FederationPullStatus was = registration.getLowestStatus(); + try { + Date now = new Date(System.currentTimeMillis()); + pull(registration); + sendStatusAcknowledgment(registration); + registration.lastPull = now; + FederationPullStatus is = registration.getLowestStatus(); + if (is.ordinal() < was.ordinal()) { + // the status for this registration has downgraded + logger.warn("Federation pull status of {0} is now {1}", registration.name, + is.name()); + if (registration.notifyOnError) { + String message = "Federation pull of " + registration.name + " @ " + + registration.url + " is now at " + is.name(); + gitblit.sendMailToAdministrators( + "Pull Status of " + registration.name + " is " + is.name(), + message); + } + } + } catch (Throwable t) { + logger.error(MessageFormat.format( + "Failed to pull from federated gitblit ({0} @ {1})", registration.name, + registration.url), t); + } finally { + reschedule(registration); + } + } + } + + /** + * Mirrors a repository and, optionally, the server's users, and/or + * configuration settings from a origin Gitblit instance. + * + * @param registration + * @throws Exception + */ + private void pull(FederationModel registration) throws Exception { + Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration, + true); + String registrationFolder = registration.folder.toLowerCase().trim(); + // confirm valid characters in server alias + Character c = StringUtils.findInvalidCharacter(registrationFolder); + if (c != null) { + logger.error(MessageFormat + .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!", + c, registrationFolder, registration.name)); + return; + } + File repositoriesFolder = gitblit.getRepositoriesFolder(); + File registrationFolderFile = new File(repositoriesFolder, registrationFolder); + registrationFolderFile.mkdirs(); + + // Clone/Pull the repository + for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) { + String cloneUrl = entry.getKey(); + RepositoryModel repository = entry.getValue(); + if (!repository.hasCommits) { + logger.warn(MessageFormat.format( + "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.", + repository.name, registration.name, registration.url)); + registration.updateStatus(repository, FederationPullStatus.SKIPPED); + continue; + } + + // Determine local repository name + String repositoryName; + if (StringUtils.isEmpty(registrationFolder)) { + repositoryName = repository.name; + } else { + repositoryName = registrationFolder + "/" + repository.name; + } + + if (registration.bare) { + // bare repository, ensure .git suffix + if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { + repositoryName += DOT_GIT_EXT; + } + } else { + // normal repository, strip .git suffix + if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { + repositoryName = repositoryName.substring(0, + repositoryName.indexOf(DOT_GIT_EXT)); + } + } + + // confirm that the origin of any pre-existing repository matches + // the clone url + String fetchHead = null; + Repository existingRepository = gitblit.getRepository(repositoryName); + + if (existingRepository == null && gitblit.isCollectingGarbage(repositoryName)) { + logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName)); + continue; + } + + if (existingRepository != null) { + StoredConfig config = existingRepository.getConfig(); + config.load(); + String origin = config.getString("remote", "origin", "url"); + RevCommit commit = JGitUtils.getCommit(existingRepository, + org.eclipse.jgit.lib.Constants.FETCH_HEAD); + if (commit != null) { + fetchHead = commit.getName(); + } + existingRepository.close(); + if (!origin.startsWith(registration.url)) { + logger.warn(MessageFormat + .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.", + repository.name, registration.name, registration.url)); + registration.updateStatus(repository, FederationPullStatus.SKIPPED); + continue; + } + } + + // clone/pull this repository + CredentialsProvider credentials = new UsernamePasswordCredentialsProvider( + Constants.FEDERATION_USER, registration.token); + logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}", + repository.name, registration.name, registration.url)); + + CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name, + cloneUrl, registration.bare, credentials); + Repository r = gitblit.getRepository(repositoryName); + RepositoryModel rm = gitblit.getRepositoryModel(repositoryName); + repository.isFrozen = registration.mirror; + if (result.createdRepository) { + // default local settings + repository.federationStrategy = FederationStrategy.EXCLUDE; + repository.isFrozen = registration.mirror; + repository.showRemoteBranches = !registration.mirror; + logger.info(MessageFormat.format(" cloning {0}", repository.name)); + registration.updateStatus(repository, FederationPullStatus.MIRRORED); + } else { + // fetch and update + boolean fetched = false; + RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD); + String newFetchHead = commit.getName(); + fetched = fetchHead == null || !fetchHead.equals(newFetchHead); + + if (registration.mirror) { + // mirror + if (fetched) { + // update local branches to match the remote tracking branches + for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) { + if (ref.displayName.startsWith("origin/")) { + String branch = org.eclipse.jgit.lib.Constants.R_HEADS + + ref.displayName.substring(ref.displayName.indexOf('/') + 1); + String hash = ref.getReferencedObjectId().getName(); + + JGitUtils.setBranchRef(r, branch, hash); + logger.info(MessageFormat.format(" resetting {0} of {1} to {2}", branch, + repository.name, hash)); + } + } + + String newHead; + if (StringUtils.isEmpty(repository.HEAD)) { + newHead = newFetchHead; + } else { + newHead = repository.HEAD; + } + JGitUtils.setHEADtoRef(r, newHead); + logger.info(MessageFormat.format(" resetting HEAD of {0} to {1}", + repository.name, newHead)); + registration.updateStatus(repository, FederationPullStatus.MIRRORED); + } else { + // indicate no commits pulled + registration.updateStatus(repository, FederationPullStatus.NOCHANGE); + } + } else { + // non-mirror + if (fetched) { + // indicate commits pulled to origin/master + registration.updateStatus(repository, FederationPullStatus.PULLED); + } else { + // indicate no commits pulled + registration.updateStatus(repository, FederationPullStatus.NOCHANGE); + } + } + + // preserve local settings + repository.isFrozen = rm.isFrozen; + repository.federationStrategy = rm.federationStrategy; + + // merge federation sets + Set<String> federationSets = new HashSet<String>(); + if (rm.federationSets != null) { + federationSets.addAll(rm.federationSets); + } + if (repository.federationSets != null) { + federationSets.addAll(repository.federationSets); + } + repository.federationSets = new ArrayList<String>(federationSets); + + // merge indexed branches + Set<String> indexedBranches = new HashSet<String>(); + if (rm.indexedBranches != null) { + indexedBranches.addAll(rm.indexedBranches); + } + if (repository.indexedBranches != null) { + indexedBranches.addAll(repository.indexedBranches); + } + repository.indexedBranches = new ArrayList<String>(indexedBranches); + + } + // only repositories that are actually _cloned_ from the origin + // Gitblit repository are marked as federated. If the origin + // is from somewhere else, these repositories are not considered + // "federated" repositories. + repository.isFederated = cloneUrl.startsWith(registration.url); + + gitblit.updateConfiguration(r, repository); + r.close(); + } + + IUserService userService = null; + + try { + // Pull USERS + // TeamModels are automatically pulled because they are contained + // within the UserModel. The UserService creates unknown teams + // and updates existing teams. + Collection<UserModel> users = FederationUtils.getUsers(registration); + if (users != null && users.size() > 0) { + File realmFile = new File(registrationFolderFile, registration.name + "_users.conf"); + realmFile.delete(); + userService = new ConfigUserService(realmFile); + for (UserModel user : users) { + userService.updateUserModel(user.username, user); + + // merge the origin permissions and origin accounts into + // the user accounts of this Gitblit instance + if (registration.mergeAccounts) { + // reparent all repository permissions if the local + // repositories are stored within subfolders + if (!StringUtils.isEmpty(registrationFolder)) { + if (user.permissions != null) { + // pulling from >= 1.2 version + Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions); + user.permissions.clear(); + for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) { + user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue()); + } + } else { + // pulling from <= 1.1 version + List<String> permissions = new ArrayList<String>(user.repositories); + user.repositories.clear(); + for (String permission : permissions) { + user.addRepositoryPermission(registrationFolder + "/" + permission); + } + } + } + + // insert new user or update local user + UserModel localUser = gitblit.getUserModel(user.username); + if (localUser == null) { + // create new local user + gitblit.updateUserModel(user.username, user, true); + } else { + // update repository permissions of local user + if (user.permissions != null) { + // pulling from >= 1.2 version + Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions); + for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) { + localUser.setRepositoryPermission(entry.getKey(), entry.getValue()); + } + } else { + // pulling from <= 1.1 version + for (String repository : user.repositories) { + localUser.addRepositoryPermission(repository); + } + } + localUser.password = user.password; + localUser.canAdmin = user.canAdmin; + gitblit.updateUserModel(localUser.username, localUser, false); + } + + for (String teamname : gitblit.getAllTeamNames()) { + TeamModel team = gitblit.getTeamModel(teamname); + if (user.isTeamMember(teamname) && !team.hasUser(user.username)) { + // new team member + team.addUser(user.username); + gitblit.updateTeamModel(teamname, team); + } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) { + // remove team member + team.removeUser(user.username); + gitblit.updateTeamModel(teamname, team); + } + + // update team repositories + TeamModel remoteTeam = user.getTeam(teamname); + if (remoteTeam != null) { + if (remoteTeam.permissions != null) { + // pulling from >= 1.2 + for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){ + team.setRepositoryPermission(entry.getKey(), entry.getValue()); + } + gitblit.updateTeamModel(teamname, team); + } else if (!ArrayUtils.isEmpty(remoteTeam.repositories)) { + // pulling from <= 1.1 + team.addRepositoryPermissions(remoteTeam.repositories); + gitblit.updateTeamModel(teamname, team); + } + } + } + } + } + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve USERS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + + try { + // Pull TEAMS + // We explicitly pull these even though they are embedded in + // UserModels because it is possible to use teams to specify + // mailing lists or push scripts without specifying users. + if (userService != null) { + Collection<TeamModel> teams = FederationUtils.getTeams(registration); + if (teams != null && teams.size() > 0) { + for (TeamModel team : teams) { + userService.updateTeamModel(team); + } + } + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + + try { + // Pull SETTINGS + Map<String, String> settings = FederationUtils.getSettings(registration); + if (settings != null && settings.size() > 0) { + Properties properties = new Properties(); + properties.putAll(settings); + FileOutputStream os = new FileOutputStream(new File(registrationFolderFile, + registration.name + "_" + Constants.PROPERTIES_FILE)); + properties.store(os, null); + os.close(); + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + + try { + // Pull SCRIPTS + Map<String, String> scripts = FederationUtils.getScripts(registration); + if (scripts != null && scripts.size() > 0) { + for (Map.Entry<String, String> script : scripts.entrySet()) { + String scriptName = script.getKey(); + if (scriptName.endsWith(".groovy")) { + scriptName = scriptName.substring(0, scriptName.indexOf(".groovy")); + } + File file = new File(registrationFolderFile, registration.name + "_" + + scriptName + ".groovy"); + FileUtils.writeContent(file, script.getValue()); + } + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + } + + /** + * Sends a status acknowledgment to the origin Gitblit instance. This + * includes the results of the federated pull. + * + * @param registration + * @throws Exception + */ + private void sendStatusAcknowledgment(FederationModel registration) throws Exception { + if (!registration.sendStatus) { + // skip status acknowledgment + return; + } + InetAddress addr = InetAddress.getLocalHost(); + String federationName = gitblit.getSettings().getString(Keys.federation.name, null); + if (StringUtils.isEmpty(federationName)) { + federationName = addr.getHostName(); + } + FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration); + logger.info(MessageFormat.format("Pull status sent to {0}", registration.url)); + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index 493f8fce..ca676ff9 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -15,50 +15,25 @@ */ package com.gitblit; -import java.io.BufferedReader; import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; -import java.lang.reflect.Type; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletContext; import javax.servlet.annotation.WebListener; -import javax.servlet.http.HttpServletRequest; -import org.apache.wicket.resource.ContextRelativeResource; -import org.apache.wicket.util.resource.ResourceStreamNotFoundException; -import org.slf4j.Logger; - -import com.gitblit.Constants.AccessPermission; -import com.gitblit.Constants.AccessRestrictionType; -import com.gitblit.Constants.FederationRequest; -import com.gitblit.Constants.FederationToken; import com.gitblit.dagger.DaggerContextListener; -import com.gitblit.fanout.FanoutNioService; -import com.gitblit.fanout.FanoutService; -import com.gitblit.fanout.FanoutSocketService; -import com.gitblit.git.GitDaemon; import com.gitblit.git.GitServlet; import com.gitblit.manager.IFederationManager; import com.gitblit.manager.IGitblitManager; @@ -67,80 +42,54 @@ import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; import com.gitblit.manager.ISessionManager; import com.gitblit.manager.IUserManager; -import com.gitblit.models.FederationModel; -import com.gitblit.models.FederationProposal; -import com.gitblit.models.FederationSet; -import com.gitblit.models.GitClientApplication; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.RepositoryUrl; -import com.gitblit.models.ServerSettings; -import com.gitblit.models.SettingModel; -import com.gitblit.models.TeamModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.ContainerUtils; -import com.gitblit.utils.FederationUtils; -import com.gitblit.utils.HttpUtils; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.JsonUtils; -import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitblitWicketFilter; -import com.gitblit.wicket.WicketUtils; -import com.google.gson.Gson; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; import dagger.ObjectGraph; /** - * GitBlit is the servlet context listener singleton that acts as the core for - * the web ui and the servlets. This class is either directly instantiated by - * the GitBlitServer class (Gitblit GO) or is reflectively instantiated by the - * servlet 3 container (Gitblit WAR or Express). + * This class is the main entry point for the entire webapp. It is a singleton + * created manually by Gitblit GO or dynamically by the WAR/Express servlet + * container. This class instantiates and starts all managers followed by + * instantiating and registering all servlets and filters. * - * This class is the central logic processor for Gitblit. All settings, user - * object, and repository object operations pass through this class. + * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to + * be modular and completely code-driven rather then relying on the fragility of + * a web.xml descriptor and the static & monolithic design previously used. * * @author James Moger * */ @WebListener -public class GitBlit extends DaggerContextListener - implements IFederationManager, - IGitblitManager { +public class GitBlit extends DaggerContextListener { private static GitBlit gitblit; - private final IStoredSettings goSettings; - - private final File goBaseFolder; - private final List<IManager> managers = new ArrayList<IManager>(); - private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10); - - private final List<FederationModel> federationRegistrations = Collections - .synchronizedList(new ArrayList<FederationModel>()); - - private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>(); - - private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>(); - - private IStoredSettings settings; - - private FanoutService fanoutService; + private final IStoredSettings goSettings; - private GitDaemon gitDaemon; + private final File goBaseFolder; + /** + * Construct a Gitblit WAR/Express context. + */ public GitBlit() { this.goSettings = null; this.goBaseFolder = null; + gitblit = this; } + /** + * Construct a Gitblit GO context. + * + * @param settings + * @param baseFolder + */ public GitBlit(IStoredSettings settings, File baseFolder) { this.goSettings = settings; this.goBaseFolder = baseFolder; @@ -148,20 +97,13 @@ public class GitBlit extends DaggerContextListener } /** - * Returns the Gitblit singleton. + * This method is only used for unit and integration testing. * - * @return gitblit singleton + * @param managerClass + * @return a manager */ - public static GitBlit self() { - return gitblit; - } - @SuppressWarnings("unchecked") - public static <X> X getManager(Class<X> managerClass) { - if (managerClass.isAssignableFrom(GitBlit.class)) { - return (X) gitblit; - } - + public static <X extends IManager> X getManager(Class<X> managerClass) { for (IManager manager : gitblit.managers) { if (managerClass.isAssignableFrom(manager.getClass())) { return (X) manager; @@ -171,786 +113,15 @@ public class GitBlit extends DaggerContextListener } /** - * Returns the path of the proposals folder. This method checks to see if - * Gitblit is running on a cloud service and may return an adjusted path. - * - * @return the proposals folder path - */ - @Override - public File getProposalsFolder() { - return getManager(IRuntimeManager.class).getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals"); - } - - /** - * Returns a list of repository URLs and the user access permission. - * - * @param request - * @param user - * @param repository - * @return a list of repository urls - */ - @Override - public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { - if (user == null) { - user = UserModel.ANONYMOUS; - } - String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); - - List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); - // http/https url - if (settings.getBoolean(Keys.git.enableGitServlet, true)) { - AccessPermission permission = user.getRepositoryPermission(repository).permission; - if (permission.exceeds(AccessPermission.NONE)) { - list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission)); - } - } - - // git daemon url - String gitDaemonUrl = getGitDaemonUrl(request, user, repository); - if (!StringUtils.isEmpty(gitDaemonUrl)) { - AccessPermission permission = getGitDaemonAccessPermission(user, repository); - if (permission.exceeds(AccessPermission.NONE)) { - list.add(new RepositoryUrl(gitDaemonUrl, permission)); - } - } - - // add all other urls - // {0} = repository - // {1} = username - for (String url : settings.getStrings(Keys.web.otherUrls)) { - if (url.contains("{1}")) { - // external url requires username, only add url IF we have one - if(!StringUtils.isEmpty(username)) { - list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null)); - } - } else { - // external url does not require username - list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null)); - } - } - return list; - } - - protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) { - StringBuilder sb = new StringBuilder(); - sb.append(HttpUtils.getGitblitURL(request)); - sb.append(Constants.GIT_PATH); - sb.append(repository.name); - - // inject username into repository url if authentication is required - if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE) - && !StringUtils.isEmpty(username)) { - sb.insert(sb.indexOf("://") + 3, username + "@"); - } - return sb.toString(); - } - - protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) { - if (gitDaemon != null) { - String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); - if (bindInterface.equals("localhost") - && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) { - // git daemon is bound to localhost and the request is from elsewhere - return null; - } - if (user.canClone(repository)) { - String servername = request.getServerName(); - String url = gitDaemon.formatUrl(servername, repository.name); - return url; - } - } - return null; - } - - protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) { - if (gitDaemon != null && user.canClone(repository)) { - AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission; - if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) { - if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) { - // can not authenticate clone via anonymous git protocol - gitDaemonPermission = AccessPermission.NONE; - } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) { - // can not authenticate push via anonymous git protocol - gitDaemonPermission = AccessPermission.CLONE; - } else { - // normal user permission - } - } - return gitDaemonPermission; - } - return AccessPermission.NONE; - } - - /** - * Returns the list of custom client applications to be used for the - * repository url panel; - * - * @return a collection of client applications - */ - @Override - public Collection<GitClientApplication> getClientApplications() { - // prefer user definitions, if they exist - File userDefs = new File(getManager(IRuntimeManager.class).getBaseFolder(), "clientapps.json"); - if (userDefs.exists()) { - Date lastModified = new Date(userDefs.lastModified()); - if (clientApplications.hasCurrent("user", lastModified)) { - return clientApplications.getObject("user"); - } else { - // (re)load user definitions - try { - InputStream is = new FileInputStream(userDefs); - Collection<GitClientApplication> clients = readClientApplications(is); - is.close(); - if (clients != null) { - clientApplications.updateObject("user", lastModified, clients); - return clients; - } - } catch (IOException e) { - logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e); - } - } - } - - // no user definitions, use system definitions - if (!clientApplications.hasCurrent("system", new Date(0))) { - try { - InputStream is = getClass().getResourceAsStream("/clientapps.json"); - Collection<GitClientApplication> clients = readClientApplications(is); - is.close(); - if (clients != null) { - clientApplications.updateObject("system", new Date(0), clients); - } - } catch (IOException e) { - logger.error("Failed to deserialize clientapps.json resource!", e); - } - } - - return clientApplications.getObject("system"); - } - - private Collection<GitClientApplication> readClientApplications(InputStream is) { - try { - Type type = new TypeToken<Collection<GitClientApplication>>() { - }.getType(); - InputStreamReader reader = new InputStreamReader(is); - Gson gson = JsonUtils.gson(); - Collection<GitClientApplication> links = gson.fromJson(reader, type); - return links; - } catch (JsonIOException e) { - logger.error("Error deserializing client applications!", e); - } catch (JsonSyntaxException e) { - logger.error("Error deserializing client applications!", e); - } - return null; - } - - /** - * Open a file resource using the Servlet container. - * @param file to open - * @return InputStream of the opened file - * @throws ResourceStreamNotFoundException - */ - public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException { - ContextRelativeResource res = WicketUtils.getResource(file); - return res.getResourceStream().getInputStream(); - } - - @Override - public UserModel getFederationUser() { - // the federation user is an administrator - UserModel federationUser = new UserModel(Constants.FEDERATION_USER); - federationUser.canAdmin = true; - return federationUser; - } - - /** - * Adds/updates a complete user object keyed by username. This method allows - * for renaming a user. - * - * @see IUserService.updateUserModel(String, UserModel) - * @param username - * @param user - * @param isCreate - * @throws GitBlitException - */ - @Override - public void updateUserModel(String username, UserModel user, boolean isCreate) - throws GitBlitException { - if (!username.equalsIgnoreCase(user.username)) { - if (getManager(IUserManager.class).getUserModel(user.username) != null) { - throw new GitBlitException(MessageFormat.format( - "Failed to rename ''{0}'' because ''{1}'' already exists.", username, - user.username)); - } - - // rename repositories and owner fields for all repositories - for (RepositoryModel model : getManager(IRepositoryManager.class).getRepositoryModels(user)) { - if (model.isUsersPersonalRepository(username)) { - // personal repository - model.addOwner(user.username); - String oldRepositoryName = model.name; - model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length()); - model.projectPath = user.getPersonalPath(); - getManager(IRepositoryManager.class).updateRepositoryModel(oldRepositoryName, model, false); - } else if (model.isOwner(username)) { - // common/shared repo - model.addOwner(user.username); - getManager(IRepositoryManager.class).updateRepositoryModel(model.name, model, false); - } - } - } - if (!getManager(IUserManager.class).updateUserModel(username, user)) { - throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!"); - } - } - - /** - * Updates the TeamModel object for the specified name. - * - * @param teamname - * @param team - * @param isCreate - */ - @Override - public void updateTeamModel(String teamname, TeamModel team, boolean isCreate) - throws GitBlitException { - if (!teamname.equalsIgnoreCase(team.name)) { - if (getManager(IUserManager.class).getTeamModel(team.name) != null) { - throw new GitBlitException(MessageFormat.format( - "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname, - team.name)); - } - } - if (!getManager(IUserManager.class).updateTeamModel(teamname, team)) { - throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!"); - } - } - - - /** - * Returns Gitblit's scheduled executor service for scheduling tasks. - * - * @return scheduledExecutor - */ - public ScheduledExecutorService executor() { - return scheduledExecutor; - } - - @Override - public boolean canFederate() { - String passphrase = settings.getString(Keys.federation.passphrase, ""); - return !StringUtils.isEmpty(passphrase); - } - - /** - * Configures this Gitblit instance to pull any registered federated gitblit - * instances. - */ - private void configureFederation() { - boolean validPassphrase = true; - String passphrase = settings.getString(Keys.federation.passphrase, ""); - if (StringUtils.isEmpty(passphrase)) { - logger.warn("Federation passphrase is blank! This server can not be PULLED from."); - validPassphrase = false; - } - if (validPassphrase) { - // standard tokens - for (FederationToken tokenType : FederationToken.values()) { - logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(), - getFederationToken(tokenType))); - } - - // federation set tokens - for (String set : settings.getStrings(Keys.federation.sets)) { - logger.info(MessageFormat.format("Federation Set {0} token = {1}", set, - getFederationToken(set))); - } - } - - // Schedule the federation executor - List<FederationModel> registrations = getFederationRegistrations(); - if (registrations.size() > 0) { - FederationPullExecutor executor = new FederationPullExecutor(registrations, true); - scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES); - } - } - - /** - * Returns the list of federated gitblit instances that this instance will - * try to pull. - * - * @return list of registered gitblit instances - */ - @Override - public List<FederationModel> getFederationRegistrations() { - if (federationRegistrations.isEmpty()) { - federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings)); - } - return federationRegistrations; - } - - /** - * Retrieve the specified federation registration. - * - * @param name - * the name of the registration - * @return a federation registration - */ - @Override - public FederationModel getFederationRegistration(String url, String name) { - // check registrations - for (FederationModel r : getFederationRegistrations()) { - if (r.name.equals(name) && r.url.equals(url)) { - return r; - } - } - - // check the results - for (FederationModel r : getFederationResultRegistrations()) { - if (r.name.equals(name) && r.url.equals(url)) { - return r; - } - } - return null; - } - - /** - * Returns the list of federation sets. - * - * @return list of federation sets - */ - @Override - public List<FederationSet> getFederationSets(String gitblitUrl) { - List<FederationSet> list = new ArrayList<FederationSet>(); - // generate standard tokens - for (FederationToken type : FederationToken.values()) { - FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type)); - fset.repositories = getRepositories(gitblitUrl, fset.token); - list.add(fset); - } - // generate tokens for federation sets - for (String set : settings.getStrings(Keys.federation.sets)) { - FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES, - getFederationToken(set)); - fset.repositories = getRepositories(gitblitUrl, fset.token); - list.add(fset); - } - return list; - } - - /** - * Returns the list of possible federation tokens for this Gitblit instance. - * - * @return list of federation tokens - */ - @Override - public List<String> getFederationTokens() { - List<String> tokens = new ArrayList<String>(); - // generate standard tokens - for (FederationToken type : FederationToken.values()) { - tokens.add(getFederationToken(type)); - } - // generate tokens for federation sets - for (String set : settings.getStrings(Keys.federation.sets)) { - tokens.add(getFederationToken(set)); - } - return tokens; - } - - /** - * Returns the specified federation token for this Gitblit instance. - * - * @param type - * @return a federation token - */ - @Override - public String getFederationToken(FederationToken type) { - return getFederationToken(type.name()); - } - - /** - * Returns the specified federation token for this Gitblit instance. - * - * @param value - * @return a federation token - */ - @Override - public String getFederationToken(String value) { - String passphrase = settings.getString(Keys.federation.passphrase, ""); - return StringUtils.getSHA1(passphrase + "-" + value); - } - - /** - * Compares the provided token with this Gitblit instance's tokens and - * determines if the requested permission may be granted to the token. - * - * @param req - * @param token - * @return true if the request can be executed - */ - @Override - public boolean validateFederationRequest(FederationRequest req, String token) { - String all = getFederationToken(FederationToken.ALL); - String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES); - String jur = getFederationToken(FederationToken.REPOSITORIES); - switch (req) { - case PULL_REPOSITORIES: - return token.equals(all) || token.equals(unr) || token.equals(jur); - case PULL_USERS: - case PULL_TEAMS: - return token.equals(all) || token.equals(unr); - case PULL_SETTINGS: - case PULL_SCRIPTS: - return token.equals(all); - default: - break; - } - return false; - } - - /** - * Acknowledge and cache the status of a remote Gitblit instance. - * - * @param identification - * the identification of the pulling Gitblit instance - * @param registration - * the registration from the pulling Gitblit instance - * @return true if acknowledged - */ - @Override - public boolean acknowledgeFederationStatus(String identification, FederationModel registration) { - // reset the url to the identification of the pulling Gitblit instance - registration.url = identification; - String id = identification; - if (!StringUtils.isEmpty(registration.folder)) { - id += "-" + registration.folder; - } - federationPullResults.put(id, registration); - return true; - } - - /** - * Returns the list of registration results. - * - * @return the list of registration results - */ - @Override - public List<FederationModel> getFederationResultRegistrations() { - return new ArrayList<FederationModel>(federationPullResults.values()); - } - - /** - * Submit a federation proposal. The proposal is cached locally and the - * Gitblit administrator(s) are notified via email. - * - * @param proposal - * the proposal - * @param gitblitUrl - * the url of your gitblit instance to send an email to - * administrators - * @return true if the proposal was submitted - */ - @Override - public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) { - // convert proposal to json - String json = JsonUtils.toJsonString(proposal); - - try { - // make the proposals folder - File proposalsFolder = getProposalsFolder(); - proposalsFolder.mkdirs(); - - // cache json to a file - File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT); - com.gitblit.utils.FileUtils.writeContent(file, json); - } catch (Exception e) { - logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e); - } - - // send an email, if possible - getManager(INotificationManager.class).sendMailToAdministrators("Federation proposal from " + proposal.url, - "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token); - return true; - } - - /** - * Returns the list of pending federation proposals - * - * @return list of federation proposals - */ - @Override - public List<FederationProposal> getPendingFederationProposals() { - List<FederationProposal> list = new ArrayList<FederationProposal>(); - File folder = getProposalsFolder(); - if (folder.exists()) { - File[] files = folder.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return file.isFile() - && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT); - } - }); - for (File file : files) { - String json = com.gitblit.utils.FileUtils.readContent(file, null); - FederationProposal proposal = JsonUtils.fromJsonString(json, - FederationProposal.class); - list.add(proposal); - } - } - return list; - } - - /** - * Get repositories for the specified token. - * - * @param gitblitUrl - * the base url of this gitblit instance - * @param token - * the federation token - * @return a map of <cloneurl, RepositoryModel> - */ - @Override - public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) { - Map<String, String> federationSets = new HashMap<String, String>(); - for (String set : settings.getStrings(Keys.federation.sets)) { - federationSets.put(getFederationToken(set), set); - } - - // Determine the Gitblit clone url - StringBuilder sb = new StringBuilder(); - sb.append(gitblitUrl); - sb.append(Constants.GIT_PATH); - sb.append("{0}"); - String cloneUrl = sb.toString(); - - // Retrieve all available repositories - UserModel user = getFederationUser(); - List<RepositoryModel> list = getManager(IRepositoryManager.class).getRepositoryModels(user); - - // create the [cloneurl, repositoryModel] map - Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); - for (RepositoryModel model : list) { - // by default, setup the url for THIS repository - String url = MessageFormat.format(cloneUrl, model.name); - switch (model.federationStrategy) { - case EXCLUDE: - // skip this repository - continue; - case FEDERATE_ORIGIN: - // federate the origin, if it is defined - if (!StringUtils.isEmpty(model.origin)) { - url = model.origin; - } - break; - default: - break; - } - - if (federationSets.containsKey(token)) { - // include repositories only for federation set - String set = federationSets.get(token); - if (model.federationSets.contains(set)) { - repositories.put(url, model); - } - } else { - // standard federation token for ALL - repositories.put(url, model); - } - } - return repositories; - } - - /** - * Creates a proposal from the token. - * - * @param gitblitUrl - * the url of this Gitblit instance - * @param token - * @return a potential proposal - */ - @Override - public FederationProposal createFederationProposal(String gitblitUrl, String token) { - FederationToken tokenType = FederationToken.REPOSITORIES; - for (FederationToken type : FederationToken.values()) { - if (token.equals(getFederationToken(type))) { - tokenType = type; - break; - } - } - Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token); - FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token, - repositories); - return proposal; - } - - /** - * Returns the proposal identified by the supplied token. - * - * @param token - * @return the specified proposal or null - */ - @Override - public FederationProposal getPendingFederationProposal(String token) { - List<FederationProposal> list = getPendingFederationProposals(); - for (FederationProposal proposal : list) { - if (proposal.token.equals(token)) { - return proposal; - } - } - return null; - } - - /** - * Deletes a pending federation proposal. - * - * @param a - * proposal - * @return true if the proposal was deleted + * Returns Gitblit's Dagger injection modules. */ @Override - public boolean deletePendingFederationProposal(FederationProposal proposal) { - File folder = getProposalsFolder(); - File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT); - return file.delete(); - } - - /** - * Parse the properties file and aggregate all the comments by the setting - * key. A setting model tracks the current value, the default value, the - * description of the setting and and directives about the setting. - * - * @return Map<String, SettingModel> - */ - private ServerSettings loadSettingModels(ServerSettings settingsModel) { - // this entire "supports" concept will go away with user service refactoring - UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT); - externalUser.password = Constants.EXTERNAL_ACCOUNT; - IUserManager userManager = getManager(IUserManager.class); - settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser); - settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser); - settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser); - settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser); - try { - // Read bundled Gitblit properties to extract setting descriptions. - // This copy is pristine and only used for populating the setting - // models map. - InputStream is = getClass().getResourceAsStream("/reference.properties"); - BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is)); - StringBuilder description = new StringBuilder(); - SettingModel setting = new SettingModel(); - String line = null; - while ((line = propertiesReader.readLine()) != null) { - if (line.length() == 0) { - description.setLength(0); - setting = new SettingModel(); - } else { - if (line.charAt(0) == '#') { - if (line.length() > 1) { - String text = line.substring(1).trim(); - if (SettingModel.CASE_SENSITIVE.equals(text)) { - setting.caseSensitive = true; - } else if (SettingModel.RESTART_REQUIRED.equals(text)) { - setting.restartRequired = true; - } else if (SettingModel.SPACE_DELIMITED.equals(text)) { - setting.spaceDelimited = true; - } else if (text.startsWith(SettingModel.SINCE)) { - try { - setting.since = text.split(" ")[1]; - } catch (Exception e) { - setting.since = text; - } - } else { - description.append(text); - description.append('\n'); - } - } - } else { - String[] kvp = line.split("=", 2); - String key = kvp[0].trim(); - setting.name = key; - setting.defaultValue = kvp[1].trim(); - setting.currentValue = setting.defaultValue; - setting.description = description.toString().trim(); - settingsModel.add(setting); - description.setLength(0); - setting = new SettingModel(); - } - } - } - propertiesReader.close(); - } catch (NullPointerException e) { - logger.error("Failed to find resource copy of gitblit.properties"); - } catch (IOException e) { - logger.error("Failed to load resource copy of gitblit.properties"); - } - return settingsModel; - } - - protected void configureFanout() { - // startup Fanout PubSub service - if (settings.getInteger(Keys.fanout.port, 0) > 0) { - String bindInterface = settings.getString(Keys.fanout.bindInterface, null); - int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT); - boolean useNio = settings.getBoolean(Keys.fanout.useNio, true); - int limit = settings.getInteger(Keys.fanout.connectionLimit, 0); - - if (useNio) { - if (StringUtils.isEmpty(bindInterface)) { - fanoutService = new FanoutNioService(port); - } else { - fanoutService = new FanoutNioService(bindInterface, port); - } - } else { - if (StringUtils.isEmpty(bindInterface)) { - fanoutService = new FanoutSocketService(port); - } else { - fanoutService = new FanoutSocketService(bindInterface, port); - } - } - - fanoutService.setConcurrentConnectionLimit(limit); - fanoutService.setAllowAllChannelAnnouncements(false); - fanoutService.start(); - } - } - - protected void configureGitDaemon() { - int port = settings.getInteger(Keys.git.daemonPort, 0); - String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); - if (port > 0) { - try { - // HACK temporary pending manager separation and injection - Gitblit gitblit = new Gitblit( - getManager(IRuntimeManager.class), - getManager(INotificationManager.class), - getManager(IUserManager.class), - getManager(ISessionManager.class), - getManager(IRepositoryManager.class), - getManager(IProjectManager.class), - this, - this); - gitDaemon = new GitDaemon(gitblit); - gitDaemon.start(); - } catch (IOException e) { - gitDaemon = null; - logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e); - } - } - } - - protected final Logger getLogger() { - return logger; - } - - protected final ScheduledExecutorService getScheduledExecutor() { - return scheduledExecutor; + protected Object [] getModules() { + return new Object [] { new DaggerModule() }; } /** - * Configure Gitblit from the web.xml, if no configuration has already been - * specified. - * - * @see ServletContextListener.contextInitialize(ServletContextEvent) + * Prepare runtime settings and start all manager instances. */ @Override protected void beforeServletInjection(ServletContext context) { @@ -958,12 +129,10 @@ public class GitBlit extends DaggerContextListener // create the runtime settings object IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); - this.settings = runtimeSettings; // XXX remove me eventually final File baseFolder; if (goSettings != null) { // Gitblit GO - logger.debug("configuring Gitblit GO"); baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings); } else { // servlet container @@ -973,11 +142,9 @@ public class GitBlit extends DaggerContextListener if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) { // RedHat OpenShift - logger.debug("configuring Gitblit Express"); baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); } else { // standard WAR - logger.debug("configuring Gitblit WAR"); baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); } @@ -985,27 +152,85 @@ public class GitBlit extends DaggerContextListener ContainerUtils.CVE_2007_0450.test(runtimeSettings); } - // Runtime manager is a container for settings and other parameters - IRuntimeManager runtime = startManager(injector, IRuntimeManager.class); + // Manually configure IRuntimeManager + logManager(IRuntimeManager.class); + IRuntimeManager runtime = injector.get(IRuntimeManager.class); runtime.setBaseFolder(baseFolder); runtime.getStatus().isGO = goSettings != null; runtime.getStatus().servletContainer = context.getServerInfo(); + runtime.start(); + managers.add(runtime); + // start all other managers startManager(injector, INotificationManager.class); startManager(injector, IUserManager.class); startManager(injector, ISessionManager.class); startManager(injector, IRepositoryManager.class); startManager(injector, IProjectManager.class); + startManager(injector, IGitblitManager.class); + startManager(injector, IFederationManager.class); + startManager(injector, IServicesManager.class); + + logger.info(""); + logger.info("All managers started."); + logger.info(""); + } + + protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) { + logManager(clazz); + X x = injector.get(clazz); + x.start(); + managers.add(x); + return x; + } + + protected void logManager(Class<? extends IManager> clazz) { + logger.info(""); + logger.info("----[{}]----", clazz.getName()); + } + + /** + * Instantiate and inject all filters and servlets into the container using + * the servlet 3 specification. + */ + @Override + protected void injectServlets(ServletContext context) { + // access restricted servlets + serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class); + serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class); + serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class); + serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class); + serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class); + + // servlets + serve(context, Constants.FEDERATION_PATH, FederationServlet.class); + serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class); + serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class); + file(context, "/robots.txt", RobotsTxtServlet.class); + file(context, "/logo.png", LogoServlet.class); - logger.info("Gitblit base folder = " + baseFolder.getAbsolutePath()); + // optional force basic authentication + filter(context, "/*", EnforceAuthenticationFilter.class, null); - loadSettingModels(runtime.getSettingsModel()); + // Wicket + String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ","); + Map<String, String> params = new HashMap<String, String>(); + params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*"); + params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); + filter(context, "/*", GitblitWicketFilter.class, params); + } - if (true/*startFederation*/) { - configureFederation(); + /** + * Gitblit is being shutdown either because the servlet container is + * shutting down or because the servlet container is re-deploying Gitblit. + */ + @Override + protected void destroyContext(ServletContext context) { + logger.info("Gitblit context destroyed by servlet container."); + for (IManager manager : managers) { + logger.debug("stopping {}", manager.getClass().getSimpleName()); + manager.stop(); } - configureFanout(); - configureGitDaemon(); } /** @@ -1023,6 +248,8 @@ public class GitBlit extends DaggerContextListener File goBaseFolder, IStoredSettings runtimeSettings) { + logger.debug("configuring Gitblit GO"); + // merge the stored settings into the runtime settings // // if runtimeSettings is also a FileSettings w/o a specified target file, @@ -1049,6 +276,7 @@ public class GitBlit extends DaggerContextListener IStoredSettings runtimeSettings) { // Gitblit is running in a standard servlet container + logger.debug("configuring Gitblit WAR"); logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>")); String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); @@ -1112,6 +340,7 @@ public class GitBlit extends DaggerContextListener IStoredSettings runtimeSettings) { // Gitblit is running in OpenShift/JBoss + logger.debug("configuring Gitblit Express"); String openShift = System.getenv("OPENSHIFT_DATA_DIR"); File base = new File(openShift); logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); @@ -1194,141 +423,4 @@ public class GitBlit extends DaggerContextListener } } } - - /** - * Gitblit is being shutdown either because the servlet container is - * shutting down or because the servlet container is re-deploying Gitblit. - */ - @Override - protected void destroyContext(ServletContext context) { - logger.info("Gitblit context destroyed by servlet container."); - for (IManager manager : managers) { - logger.debug("stopping {}", manager.getClass().getSimpleName()); - manager.stop(); - } - - scheduledExecutor.shutdownNow(); - if (fanoutService != null) { - fanoutService.stop(); - } - if (gitDaemon != null) { - gitDaemon.stop(); - } - } - - /** - * Creates a personal fork of the specified repository. The clone is view - * restricted by default and the owner of the source repository is given - * access to the clone. - * - * @param repository - * @param user - * @return the repository model of the fork, if successful - * @throws GitBlitException - */ - @Override - public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException { - String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name))); - String fromUrl = MessageFormat.format("file://{0}/{1}", getManager(IRepositoryManager.class).getRepositoriesFolder().getAbsolutePath(), repository.name); - - // clone the repository - try { - JGitUtils.cloneRepository(getManager(IRepositoryManager.class).getRepositoriesFolder(), cloneName, fromUrl, true, null); - } catch (Exception e) { - throw new GitBlitException(e); - } - - // create a Gitblit repository model for the clone - RepositoryModel cloneModel = repository.cloneAs(cloneName); - // owner has REWIND/RW+ permissions - cloneModel.addOwner(user.username); - getManager(IRepositoryManager.class).updateRepositoryModel(cloneName, cloneModel, false); - - // add the owner of the source repository to the clone's access list - if (!ArrayUtils.isEmpty(repository.owners)) { - for (String owner : repository.owners) { - UserModel originOwner = getManager(IUserManager.class).getUserModel(owner); - if (originOwner != null) { - originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE); - updateUserModel(originOwner.username, originOwner, false); - } - } - } - - // grant origin's user list clone permission to fork - List<String> users = getManager(IRepositoryManager.class).getRepositoryUsers(repository); - List<UserModel> cloneUsers = new ArrayList<UserModel>(); - for (String name : users) { - if (!name.equalsIgnoreCase(user.username)) { - UserModel cloneUser = getManager(IUserManager.class).getUserModel(name); - if (cloneUser.canClone(repository)) { - // origin user can clone origin, grant clone access to fork - cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE); - } - cloneUsers.add(cloneUser); - } - } - getManager(IUserManager.class).updateUserModels(cloneUsers); - - // grant origin's team list clone permission to fork - List<String> teams = getManager(IRepositoryManager.class).getRepositoryTeams(repository); - List<TeamModel> cloneTeams = new ArrayList<TeamModel>(); - for (String name : teams) { - TeamModel cloneTeam = getManager(IUserManager.class).getTeamModel(name); - if (cloneTeam.canClone(repository)) { - // origin team can clone origin, grant clone access to fork - cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE); - } - cloneTeams.add(cloneTeam); - } - getManager(IUserManager.class).updateTeamModels(cloneTeams); - - // add this clone to the cached model - getManager(IRepositoryManager.class).addToCachedRepositoryList(cloneModel); - return cloneModel; - } - - @Override - protected Object [] getModules() { - return new Object [] { new DaggerModule(this) }; - } - - protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) { - logger.debug("injecting and starting {}", clazz.getSimpleName()); - X x = injector.get(clazz); - x.setup(); - managers.add(x); - return x; - } - - /** - * Instantiate and inject all filters and servlets into the container using - * the servlet 3 specification. - */ - @Override - protected void injectServlets(ServletContext context) { - // access restricted servlets - serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class); - serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class); - serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class); - serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class); - serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class); - - // servlets - serve(context, Constants.FEDERATION_PATH, FederationServlet.class); - serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class); - serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class); - file(context, "/robots.txt", RobotsTxtServlet.class); - file(context, "/logo.png", LogoServlet.class); - - // optional force basic authentication - filter(context, "/*", EnforceAuthenticationFilter.class, null); - - // Wicket - String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ","); - Map<String, String> params = new HashMap<String, String>(); - params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*"); - params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); - filter(context, "/*", GitblitWicketFilter.class, params); - } } diff --git a/src/main/java/com/gitblit/Gitblit.java b/src/main/java/com/gitblit/Gitblit.java index 7a5c73ea..64316bb4 100644 --- a/src/main/java/com/gitblit/Gitblit.java +++ b/src/main/java/com/gitblit/Gitblit.java @@ -22,8 +22,6 @@ import java.util.List; import java.util.Map; import java.util.TimeZone; -import javax.inject.Inject; -import javax.inject.Singleton; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -33,7 +31,6 @@ import com.gitblit.Constants.FederationRequest; import com.gitblit.Constants.FederationToken; import com.gitblit.manager.IFederationManager; import com.gitblit.manager.IGitblitManager; -import com.gitblit.manager.IManager; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; @@ -65,7 +62,6 @@ import com.gitblit.models.UserModel; * @author James Moger * */ -@Singleton public class Gitblit implements IRuntimeManager, INotificationManager, IUserManager, @@ -87,11 +83,10 @@ public class Gitblit implements IRuntimeManager, private final IProjectManager projectManager; - private final IFederationManager federationManager; - private final IGitblitManager gitblitManager; - @Inject + private final IFederationManager federationManager; + public Gitblit( IRuntimeManager runtimeManager, INotificationManager notificationManager, @@ -99,8 +94,8 @@ public class Gitblit implements IRuntimeManager, ISessionManager sessionManager, IRepositoryManager repositoryManager, IProjectManager projectManager, - IFederationManager federationManager, - IGitblitManager gitblitManager) { + IGitblitManager gitblitManager, + IFederationManager federationManager) { this.runtimeManager = runtimeManager; this.notificationManager = notificationManager; @@ -108,17 +103,17 @@ public class Gitblit implements IRuntimeManager, this.sessionManager = sessionManager; this.repositoryManager = repositoryManager; this.projectManager = projectManager; - this.federationManager = federationManager; this.gitblitManager = gitblitManager; + this.federationManager = federationManager; } @Override - public IManager setup() { + public Gitblit start() { return this; } @Override - public IManager stop() { + public Gitblit stop() { return this; } diff --git a/src/main/java/com/gitblit/manager/FederationManager.java b/src/main/java/com/gitblit/manager/FederationManager.java new file mode 100644 index 00000000..07d20184 --- /dev/null +++ b/src/main/java/com/gitblit/manager/FederationManager.java @@ -0,0 +1,454 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + +import java.io.File; +import java.io.FileFilter; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.Constants.FederationRequest; +import com.gitblit.Constants.FederationToken; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.models.FederationModel; +import com.gitblit.models.FederationProposal; +import com.gitblit.models.FederationSet; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.FederationUtils; +import com.gitblit.utils.JsonUtils; +import com.gitblit.utils.StringUtils; + +/** + * Federation manager controls all aspects of handling federation sets, tokens, + * and proposals. + * + * @author James Moger + * + */ +public class FederationManager implements IFederationManager { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final List<FederationModel> federationRegistrations = Collections + .synchronizedList(new ArrayList<FederationModel>()); + + private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>(); + + private final IStoredSettings settings; + + private final IRuntimeManager runtimeManager; + + private final INotificationManager notificationManager; + + private final IRepositoryManager repositoryManager; + + public FederationManager( + IRuntimeManager runtimeManager, + INotificationManager notificationManager, + IUserManager userManager, + IRepositoryManager repositoryManager) { + + this.settings = runtimeManager.getSettings(); + this.runtimeManager = runtimeManager; + this.notificationManager = notificationManager; + this.repositoryManager = repositoryManager; + } + + @Override + public FederationManager start() { + return this; + } + + @Override + public FederationManager stop() { + return this; + } + + /** + * Returns the path of the proposals folder. This method checks to see if + * Gitblit is running on a cloud service and may return an adjusted path. + * + * @return the proposals folder path + */ + @Override + public File getProposalsFolder() { + return runtimeManager.getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals"); + } + + @Override + public UserModel getFederationUser() { + // the federation user is an administrator + UserModel federationUser = new UserModel(Constants.FEDERATION_USER); + federationUser.canAdmin = true; + return federationUser; + } + + @Override + public boolean canFederate() { + String passphrase = settings.getString(Keys.federation.passphrase, ""); + return !StringUtils.isEmpty(passphrase); + } + + /** + * Returns the list of federated gitblit instances that this instance will + * try to pull. + * + * @return list of registered gitblit instances + */ + @Override + public List<FederationModel> getFederationRegistrations() { + if (federationRegistrations.isEmpty()) { + federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings)); + } + return federationRegistrations; + } + + /** + * Retrieve the specified federation registration. + * + * @param name + * the name of the registration + * @return a federation registration + */ + @Override + public FederationModel getFederationRegistration(String url, String name) { + // check registrations + for (FederationModel r : getFederationRegistrations()) { + if (r.name.equals(name) && r.url.equals(url)) { + return r; + } + } + + // check the results + for (FederationModel r : getFederationResultRegistrations()) { + if (r.name.equals(name) && r.url.equals(url)) { + return r; + } + } + return null; + } + + /** + * Returns the list of federation sets. + * + * @return list of federation sets + */ + @Override + public List<FederationSet> getFederationSets(String gitblitUrl) { + List<FederationSet> list = new ArrayList<FederationSet>(); + // generate standard tokens + for (FederationToken type : FederationToken.values()) { + FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type)); + fset.repositories = getRepositories(gitblitUrl, fset.token); + list.add(fset); + } + // generate tokens for federation sets + for (String set : settings.getStrings(Keys.federation.sets)) { + FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES, + getFederationToken(set)); + fset.repositories = getRepositories(gitblitUrl, fset.token); + list.add(fset); + } + return list; + } + + /** + * Returns the list of possible federation tokens for this Gitblit instance. + * + * @return list of federation tokens + */ + @Override + public List<String> getFederationTokens() { + List<String> tokens = new ArrayList<String>(); + // generate standard tokens + for (FederationToken type : FederationToken.values()) { + tokens.add(getFederationToken(type)); + } + // generate tokens for federation sets + for (String set : settings.getStrings(Keys.federation.sets)) { + tokens.add(getFederationToken(set)); + } + return tokens; + } + + /** + * Returns the specified federation token for this Gitblit instance. + * + * @param type + * @return a federation token + */ + @Override + public String getFederationToken(FederationToken type) { + return getFederationToken(type.name()); + } + + /** + * Returns the specified federation token for this Gitblit instance. + * + * @param value + * @return a federation token + */ + @Override + public String getFederationToken(String value) { + String passphrase = settings.getString(Keys.federation.passphrase, ""); + return StringUtils.getSHA1(passphrase + "-" + value); + } + + /** + * Compares the provided token with this Gitblit instance's tokens and + * determines if the requested permission may be granted to the token. + * + * @param req + * @param token + * @return true if the request can be executed + */ + @Override + public boolean validateFederationRequest(FederationRequest req, String token) { + String all = getFederationToken(FederationToken.ALL); + String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES); + String jur = getFederationToken(FederationToken.REPOSITORIES); + switch (req) { + case PULL_REPOSITORIES: + return token.equals(all) || token.equals(unr) || token.equals(jur); + case PULL_USERS: + case PULL_TEAMS: + return token.equals(all) || token.equals(unr); + case PULL_SETTINGS: + case PULL_SCRIPTS: + return token.equals(all); + default: + break; + } + return false; + } + + /** + * Acknowledge and cache the status of a remote Gitblit instance. + * + * @param identification + * the identification of the pulling Gitblit instance + * @param registration + * the registration from the pulling Gitblit instance + * @return true if acknowledged + */ + @Override + public boolean acknowledgeFederationStatus(String identification, FederationModel registration) { + // reset the url to the identification of the pulling Gitblit instance + registration.url = identification; + String id = identification; + if (!StringUtils.isEmpty(registration.folder)) { + id += "-" + registration.folder; + } + federationPullResults.put(id, registration); + return true; + } + + /** + * Returns the list of registration results. + * + * @return the list of registration results + */ + @Override + public List<FederationModel> getFederationResultRegistrations() { + return new ArrayList<FederationModel>(federationPullResults.values()); + } + + /** + * Submit a federation proposal. The proposal is cached locally and the + * Gitblit administrator(s) are notified via email. + * + * @param proposal + * the proposal + * @param gitblitUrl + * the url of your gitblit instance to send an email to + * administrators + * @return true if the proposal was submitted + */ + @Override + public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) { + // convert proposal to json + String json = JsonUtils.toJsonString(proposal); + + try { + // make the proposals folder + File proposalsFolder = getProposalsFolder(); + proposalsFolder.mkdirs(); + + // cache json to a file + File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT); + com.gitblit.utils.FileUtils.writeContent(file, json); + } catch (Exception e) { + logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e); + } + + // send an email, if possible + notificationManager.sendMailToAdministrators("Federation proposal from " + proposal.url, + "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token); + return true; + } + + /** + * Returns the list of pending federation proposals + * + * @return list of federation proposals + */ + @Override + public List<FederationProposal> getPendingFederationProposals() { + List<FederationProposal> list = new ArrayList<FederationProposal>(); + File folder = getProposalsFolder(); + if (folder.exists()) { + File[] files = folder.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.isFile() + && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT); + } + }); + for (File file : files) { + String json = com.gitblit.utils.FileUtils.readContent(file, null); + FederationProposal proposal = JsonUtils.fromJsonString(json, + FederationProposal.class); + list.add(proposal); + } + } + return list; + } + + /** + * Get repositories for the specified token. + * + * @param gitblitUrl + * the base url of this gitblit instance + * @param token + * the federation token + * @return a map of <cloneurl, RepositoryModel> + */ + @Override + public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) { + Map<String, String> federationSets = new HashMap<String, String>(); + for (String set : settings.getStrings(Keys.federation.sets)) { + federationSets.put(getFederationToken(set), set); + } + + // Determine the Gitblit clone url + StringBuilder sb = new StringBuilder(); + sb.append(gitblitUrl); + sb.append(Constants.GIT_PATH); + sb.append("{0}"); + String cloneUrl = sb.toString(); + + // Retrieve all available repositories + UserModel user = getFederationUser(); + List<RepositoryModel> list = repositoryManager.getRepositoryModels(user); + + // create the [cloneurl, repositoryModel] map + Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); + for (RepositoryModel model : list) { + // by default, setup the url for THIS repository + String url = MessageFormat.format(cloneUrl, model.name); + switch (model.federationStrategy) { + case EXCLUDE: + // skip this repository + continue; + case FEDERATE_ORIGIN: + // federate the origin, if it is defined + if (!StringUtils.isEmpty(model.origin)) { + url = model.origin; + } + break; + default: + break; + } + + if (federationSets.containsKey(token)) { + // include repositories only for federation set + String set = federationSets.get(token); + if (model.federationSets.contains(set)) { + repositories.put(url, model); + } + } else { + // standard federation token for ALL + repositories.put(url, model); + } + } + return repositories; + } + + /** + * Creates a proposal from the token. + * + * @param gitblitUrl + * the url of this Gitblit instance + * @param token + * @return a potential proposal + */ + @Override + public FederationProposal createFederationProposal(String gitblitUrl, String token) { + FederationToken tokenType = FederationToken.REPOSITORIES; + for (FederationToken type : FederationToken.values()) { + if (token.equals(getFederationToken(type))) { + tokenType = type; + break; + } + } + Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token); + FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token, + repositories); + return proposal; + } + + /** + * Returns the proposal identified by the supplied token. + * + * @param token + * @return the specified proposal or null + */ + @Override + public FederationProposal getPendingFederationProposal(String token) { + List<FederationProposal> list = getPendingFederationProposals(); + for (FederationProposal proposal : list) { + if (proposal.token.equals(token)) { + return proposal; + } + } + return null; + } + + /** + * Deletes a pending federation proposal. + * + * @param a + * proposal + * @return true if the proposal was deleted + */ + @Override + public boolean deletePendingFederationProposal(FederationProposal proposal) { + File folder = getProposalsFolder(); + File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT); + return file.delete(); + } +} diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java new file mode 100644 index 00000000..2e6a33e3 --- /dev/null +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -0,0 +1,463 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.GitBlitException; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.models.GitClientApplication; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.RepositoryUrl; +import com.gitblit.models.ServerSettings; +import com.gitblit.models.SettingModel; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.HttpUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.JsonUtils; +import com.gitblit.utils.ObjectCache; +import com.gitblit.utils.StringUtils; +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +public class GitblitManager implements IGitblitManager { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>(); + + private final IStoredSettings settings; + + private final IRuntimeManager runtimeManager; + + private final IUserManager userManager; + + private final IRepositoryManager repositoryManager; + + public GitblitManager( + IRuntimeManager runtimeManager, + IUserManager userManager, + IRepositoryManager repositoryManager) { + + this.settings = runtimeManager.getSettings(); + this.runtimeManager = runtimeManager; + this.userManager = userManager; + this.repositoryManager = repositoryManager; + } + + @Override + public GitblitManager start() { + loadSettingModels(runtimeManager.getSettingsModel()); + return this; + } + + @Override + public GitblitManager stop() { + return this; + } + + /** + * Parse the properties file and aggregate all the comments by the setting + * key. A setting model tracks the current value, the default value, the + * description of the setting and and directives about the setting. + * + * @return Map<String, SettingModel> + */ + private void loadSettingModels(ServerSettings settingsModel) { + // this entire "supports" concept will go away with user service refactoring + UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT); + externalUser.password = Constants.EXTERNAL_ACCOUNT; + settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser); + settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser); + settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser); + settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser); + try { + // Read bundled Gitblit properties to extract setting descriptions. + // This copy is pristine and only used for populating the setting + // models map. + InputStream is = getClass().getResourceAsStream("/reference.properties"); + BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is)); + StringBuilder description = new StringBuilder(); + SettingModel setting = new SettingModel(); + String line = null; + while ((line = propertiesReader.readLine()) != null) { + if (line.length() == 0) { + description.setLength(0); + setting = new SettingModel(); + } else { + if (line.charAt(0) == '#') { + if (line.length() > 1) { + String text = line.substring(1).trim(); + if (SettingModel.CASE_SENSITIVE.equals(text)) { + setting.caseSensitive = true; + } else if (SettingModel.RESTART_REQUIRED.equals(text)) { + setting.restartRequired = true; + } else if (SettingModel.SPACE_DELIMITED.equals(text)) { + setting.spaceDelimited = true; + } else if (text.startsWith(SettingModel.SINCE)) { + try { + setting.since = text.split(" ")[1]; + } catch (Exception e) { + setting.since = text; + } + } else { + description.append(text); + description.append('\n'); + } + } + } else { + String[] kvp = line.split("=", 2); + String key = kvp[0].trim(); + setting.name = key; + setting.defaultValue = kvp[1].trim(); + setting.currentValue = setting.defaultValue; + setting.description = description.toString().trim(); + settingsModel.add(setting); + description.setLength(0); + setting = new SettingModel(); + } + } + } + propertiesReader.close(); + } catch (NullPointerException e) { + logger.error("Failed to find resource copy of gitblit.properties"); + } catch (IOException e) { + logger.error("Failed to load resource copy of gitblit.properties"); + } + } + + /** + * Returns a list of repository URLs and the user access permission. + * + * @param request + * @param user + * @param repository + * @return a list of repository urls + */ + @Override + public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { + if (user == null) { + user = UserModel.ANONYMOUS; + } + String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); + + List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); + // http/https url + if (settings.getBoolean(Keys.git.enableGitServlet, true)) { + AccessPermission permission = user.getRepositoryPermission(repository).permission; + if (permission.exceeds(AccessPermission.NONE)) { + list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission)); + } + } + + // git daemon url + String gitDaemonUrl = getGitDaemonUrl(request, user, repository); + if (!StringUtils.isEmpty(gitDaemonUrl)) { + AccessPermission permission = getGitDaemonAccessPermission(user, repository); + if (permission.exceeds(AccessPermission.NONE)) { + list.add(new RepositoryUrl(gitDaemonUrl, permission)); + } + } + + // add all other urls + // {0} = repository + // {1} = username + for (String url : settings.getStrings(Keys.web.otherUrls)) { + if (url.contains("{1}")) { + // external url requires username, only add url IF we have one + if (!StringUtils.isEmpty(username)) { + list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null)); + } + } else { + // external url does not require username + list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null)); + } + } + return list; + } + + protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) { + StringBuilder sb = new StringBuilder(); + sb.append(HttpUtils.getGitblitURL(request)); + sb.append(Constants.GIT_PATH); + sb.append(repository.name); + + // inject username into repository url if authentication is required + if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE) + && !StringUtils.isEmpty(username)) { + sb.insert(sb.indexOf("://") + 3, username + "@"); + } + return sb.toString(); + } + + protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) { +// if (gitDaemon != null) { +// String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); +// if (bindInterface.equals("localhost") +// && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) { +// // git daemon is bound to localhost and the request is from elsewhere +// return null; +// } +// if (user.canClone(repository)) { +// String servername = request.getServerName(); +// String url = gitDaemon.formatUrl(servername, repository.name); +// return url; +// } +// } + return null; + } + + protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) { +// if (gitDaemon != null && user.canClone(repository)) { +// AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission; +// if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) { +// if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) { +// // can not authenticate clone via anonymous git protocol +// gitDaemonPermission = AccessPermission.NONE; +// } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) { +// // can not authenticate push via anonymous git protocol +// gitDaemonPermission = AccessPermission.CLONE; +// } else { +// // normal user permission +// } +// } +// return gitDaemonPermission; +// } + return AccessPermission.NONE; + } + + /** + * Returns the list of custom client applications to be used for the + * repository url panel; + * + * @return a collection of client applications + */ + @Override + public Collection<GitClientApplication> getClientApplications() { + // prefer user definitions, if they exist + File userDefs = new File(runtimeManager.getBaseFolder(), "clientapps.json"); + if (userDefs.exists()) { + Date lastModified = new Date(userDefs.lastModified()); + if (clientApplications.hasCurrent("user", lastModified)) { + return clientApplications.getObject("user"); + } else { + // (re)load user definitions + try { + InputStream is = new FileInputStream(userDefs); + Collection<GitClientApplication> clients = readClientApplications(is); + is.close(); + if (clients != null) { + clientApplications.updateObject("user", lastModified, clients); + return clients; + } + } catch (IOException e) { + logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e); + } + } + } + + // no user definitions, use system definitions + if (!clientApplications.hasCurrent("system", new Date(0))) { + try { + InputStream is = getClass().getResourceAsStream("/clientapps.json"); + Collection<GitClientApplication> clients = readClientApplications(is); + is.close(); + if (clients != null) { + clientApplications.updateObject("system", new Date(0), clients); + } + } catch (IOException e) { + logger.error("Failed to deserialize clientapps.json resource!", e); + } + } + + return clientApplications.getObject("system"); + } + + private Collection<GitClientApplication> readClientApplications(InputStream is) { + try { + Type type = new TypeToken<Collection<GitClientApplication>>() { + }.getType(); + InputStreamReader reader = new InputStreamReader(is); + Gson gson = JsonUtils.gson(); + Collection<GitClientApplication> links = gson.fromJson(reader, type); + return links; + } catch (JsonIOException e) { + logger.error("Error deserializing client applications!", e); + } catch (JsonSyntaxException e) { + logger.error("Error deserializing client applications!", e); + } + return null; + } + + /** + * Creates a personal fork of the specified repository. The clone is view + * restricted by default and the owner of the source repository is given + * access to the clone. + * + * @param repository + * @param user + * @return the repository model of the fork, if successful + * @throws GitBlitException + */ + @Override + public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException { + String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name))); + String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name); + + // clone the repository + try { + JGitUtils.cloneRepository(repositoryManager.getRepositoriesFolder(), cloneName, fromUrl, true, null); + } catch (Exception e) { + throw new GitBlitException(e); + } + + // create a Gitblit repository model for the clone + RepositoryModel cloneModel = repository.cloneAs(cloneName); + // owner has REWIND/RW+ permissions + cloneModel.addOwner(user.username); + repositoryManager.updateRepositoryModel(cloneName, cloneModel, false); + + // add the owner of the source repository to the clone's access list + if (!ArrayUtils.isEmpty(repository.owners)) { + for (String owner : repository.owners) { + UserModel originOwner = userManager.getUserModel(owner); + if (originOwner != null) { + originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE); + updateUserModel(originOwner.username, originOwner, false); + } + } + } + + // grant origin's user list clone permission to fork + List<String> users = repositoryManager.getRepositoryUsers(repository); + List<UserModel> cloneUsers = new ArrayList<UserModel>(); + for (String name : users) { + if (!name.equalsIgnoreCase(user.username)) { + UserModel cloneUser = userManager.getUserModel(name); + if (cloneUser.canClone(repository)) { + // origin user can clone origin, grant clone access to fork + cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE); + } + cloneUsers.add(cloneUser); + } + } + userManager.updateUserModels(cloneUsers); + + // grant origin's team list clone permission to fork + List<String> teams = repositoryManager.getRepositoryTeams(repository); + List<TeamModel> cloneTeams = new ArrayList<TeamModel>(); + for (String name : teams) { + TeamModel cloneTeam = userManager.getTeamModel(name); + if (cloneTeam.canClone(repository)) { + // origin team can clone origin, grant clone access to fork + cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE); + } + cloneTeams.add(cloneTeam); + } + userManager.updateTeamModels(cloneTeams); + + // add this clone to the cached model + repositoryManager.addToCachedRepositoryList(cloneModel); + return cloneModel; + } + + /** + * Adds/updates a complete user object keyed by username. This method allows + * for renaming a user. + * + * @see IUserService.updateUserModel(String, UserModel) + * @param username + * @param user + * @param isCreate + * @throws GitBlitException + */ + @Override + public void updateUserModel(String username, UserModel user, boolean isCreate) + throws GitBlitException { + if (!username.equalsIgnoreCase(user.username)) { + if (userManager.getUserModel(user.username) != null) { + throw new GitBlitException(MessageFormat.format( + "Failed to rename ''{0}'' because ''{1}'' already exists.", username, + user.username)); + } + + // rename repositories and owner fields for all repositories + for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) { + if (model.isUsersPersonalRepository(username)) { + // personal repository + model.addOwner(user.username); + String oldRepositoryName = model.name; + model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length()); + model.projectPath = user.getPersonalPath(); + repositoryManager.updateRepositoryModel(oldRepositoryName, model, false); + } else if (model.isOwner(username)) { + // common/shared repo + model.addOwner(user.username); + repositoryManager.updateRepositoryModel(model.name, model, false); + } + } + } + if (!userManager.updateUserModel(username, user)) { + throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!"); + } + } + + /** + * Updates the TeamModel object for the specified name. + * + * @param teamname + * @param team + * @param isCreate + */ + @Override + public void updateTeamModel(String teamname, TeamModel team, boolean isCreate) + throws GitBlitException { + if (!teamname.equalsIgnoreCase(team.name)) { + if (userManager.getTeamModel(team.name) != null) { + throw new GitBlitException(MessageFormat.format( + "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname, + team.name)); + } + } + if (!userManager.updateTeamModel(teamname, team)) { + throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!"); + } + } +} diff --git a/src/main/java/com/gitblit/manager/IFederationManager.java b/src/main/java/com/gitblit/manager/IFederationManager.java index debe362b..5afdeea1 100644 --- a/src/main/java/com/gitblit/manager/IFederationManager.java +++ b/src/main/java/com/gitblit/manager/IFederationManager.java @@ -27,7 +27,7 @@ import com.gitblit.models.FederationSet; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; -public interface IFederationManager { +public interface IFederationManager extends IManager { /** * Returns the path of the proposals folder. This method checks to see if diff --git a/src/main/java/com/gitblit/manager/IGitblitManager.java b/src/main/java/com/gitblit/manager/IGitblitManager.java index 2f5295bd..21c9e6a8 100644 --- a/src/main/java/com/gitblit/manager/IGitblitManager.java +++ b/src/main/java/com/gitblit/manager/IGitblitManager.java @@ -27,7 +27,7 @@ import com.gitblit.models.RepositoryUrl; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; -public interface IGitblitManager { +public interface IGitblitManager extends IManager { /** * Returns a list of repository URLs and the user access permission. diff --git a/src/main/java/com/gitblit/manager/IManager.java b/src/main/java/com/gitblit/manager/IManager.java index 955c6100..115831b9 100644 --- a/src/main/java/com/gitblit/manager/IManager.java +++ b/src/main/java/com/gitblit/manager/IManager.java @@ -17,7 +17,8 @@ package com.gitblit.manager; public interface IManager { - IManager setup(); + IManager start(); IManager stop(); + } diff --git a/src/main/java/com/gitblit/manager/IServicesManager.java b/src/main/java/com/gitblit/manager/IServicesManager.java new file mode 100644 index 00000000..9fdd063c --- /dev/null +++ b/src/main/java/com/gitblit/manager/IServicesManager.java @@ -0,0 +1,21 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + + +public interface IServicesManager extends IManager { + +} diff --git a/src/main/java/com/gitblit/manager/NotificationManager.java b/src/main/java/com/gitblit/manager/NotificationManager.java index eae563f3..e38e1f1f 100644 --- a/src/main/java/com/gitblit/manager/NotificationManager.java +++ b/src/main/java/com/gitblit/manager/NotificationManager.java @@ -58,18 +58,19 @@ public class NotificationManager implements INotificationManager { } @Override - public IManager setup() { + public NotificationManager start() { if (mailExecutor.isReady()) { - logger.info("Mail executor is scheduled to process the message queue every 2 minutes."); - scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES); + int period = 2; + logger.info("Mail service will process the queue every {} minutes.", period); + scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, period, TimeUnit.MINUTES); } else { - logger.warn("Mail server is not properly configured. Mail services disabled."); + logger.warn("Mail service disabled."); } return this; } @Override - public IManager stop() { + public NotificationManager stop() { scheduledExecutor.shutdownNow(); return this; } diff --git a/src/main/java/com/gitblit/manager/ProjectManager.java b/src/main/java/com/gitblit/manager/ProjectManager.java index 83a63102..b30f4f17 100644 --- a/src/main/java/com/gitblit/manager/ProjectManager.java +++ b/src/main/java/com/gitblit/manager/ProjectManager.java @@ -80,7 +80,7 @@ public class ProjectManager implements IProjectManager { } @Override - public IManager setup() { + public ProjectManager start() { // load and cache the project metadata projectConfigs = new FileBasedConfig(runtimeManager.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect()); getProjectConfigs(); @@ -89,7 +89,7 @@ public class ProjectManager implements IProjectManager { } @Override - public IManager stop() { + public ProjectManager stop() { return this; } diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java index 9d38b300..4845e23e 100644 --- a/src/main/java/com/gitblit/manager/RepositoryManager.java +++ b/src/main/java/com/gitblit/manager/RepositoryManager.java @@ -135,8 +135,8 @@ public class RepositoryManager implements IRepositoryManager { } @Override - public IManager setup() { - logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath()); + public RepositoryManager start() { + logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath()); // initialize utilities String prefix = settings.getString(Keys.git.userRepositoryPrefix, "~"); @@ -147,7 +147,7 @@ public class RepositoryManager implements IRepositoryManager { // build initial repository list if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { - logger.info("Identifying available repositories..."); + logger.info("Identifying repositories..."); getRepositoryList(); } @@ -161,7 +161,7 @@ public class RepositoryManager implements IRepositoryManager { } @Override - public IManager stop() { + public RepositoryManager stop() { scheduledExecutor.shutdownNow(); luceneExecutor.close(); gcExecutor.close(); @@ -1645,15 +1645,16 @@ public class RepositoryManager implements IRepositoryManager { protected void configureLuceneIndexing() { luceneExecutor = new LuceneExecutor(settings, this); - scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES); - logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes."); + int period = 2; + scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, period, TimeUnit.MINUTES); + logger.info("Lucene will process indexed branches every {} minutes.", period); } protected void configureGarbageCollector() { // schedule gc engine gcExecutor = new GCExecutor(settings, this); if (gcExecutor.isReady()) { - logger.info("GC executor is scheduled to scan repositories every 24 hours."); + logger.info("Garbage Collector (GC) will scan repositories every 24 hours."); Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0)); c.set(Calendar.MINUTE, 0); @@ -1673,6 +1674,8 @@ public class RepositoryManager implements IRepositoryManager { } logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when)); scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60 * 24, TimeUnit.MINUTES); + } else { + logger.info("Garbage Collector (GC) is disabled."); } } @@ -1685,8 +1688,10 @@ public class RepositoryManager implements IRepositoryManager { } int delay = 1; scheduledExecutor.scheduleAtFixedRate(mirrorExecutor, delay, mins, TimeUnit.MINUTES); - logger.info("Mirror executor is scheduled to fetch updates every {} minutes.", mins); + logger.info("Mirror service will fetch updates every {} minutes.", mins); logger.info("Next scheduled mirror fetch is in {} minutes", delay); + } else { + logger.info("Mirror service is disabled."); } } @@ -1717,12 +1722,12 @@ public class RepositoryManager implements IRepositoryManager { protected void configureCommitCache() { int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14); if (daysToCache <= 0) { - logger.info("commit cache disabled"); + logger.info("Commit cache is disabled"); } else { long start = System.nanoTime(); long repoCount = 0; long commitCount = 0; - logger.info(MessageFormat.format("preparing {0} day commit cache. please wait...", daysToCache)); + logger.info(MessageFormat.format("Preparing {0} day commit cache. please wait...", daysToCache)); CommitCache.instance().setCacheDays(daysToCache); Date cutoff = CommitCache.instance().getCutoffDate(); for (String repositoryName : getRepositoryList()) { diff --git a/src/main/java/com/gitblit/manager/RuntimeManager.java b/src/main/java/com/gitblit/manager/RuntimeManager.java index cfb4543e..45d1ea12 100644 --- a/src/main/java/com/gitblit/manager/RuntimeManager.java +++ b/src/main/java/com/gitblit/manager/RuntimeManager.java @@ -40,23 +40,29 @@ public class RuntimeManager implements IRuntimeManager { private final ServerStatus serverStatus; - private TimeZone timezone; + private final ServerSettings settingsModel; private File baseFolder; - private ServerSettings settingsModel; + private TimeZone timezone; public RuntimeManager(IStoredSettings settings) { + this(settings, null); + } + + public RuntimeManager(IStoredSettings settings, File baseFolder) { this.settings = settings; this.settingsModel = new ServerSettings(); this.serverStatus = new ServerStatus(); + this.baseFolder = baseFolder == null ? new File("") : baseFolder; } @Override - public RuntimeManager setup() { - logger.info("Gitblit settings = " + settings.toString()); - logTimezone("JVM", TimeZone.getDefault()); - logTimezone(Constants.NAME, getTimezone()); + public RuntimeManager start() { + logger.info("Basefolder : " + baseFolder.getAbsolutePath()); + logger.info("Settings : " + settings.toString()); + logTimezone("JVM timezone: ", TimeZone.getDefault()); + logTimezone("App timezone: ", getTimezone()); return this; } @@ -121,7 +127,7 @@ public class RuntimeManager implements IRuntimeManager { @Override public TimeZone getTimezone() { if (timezone == null) { - String tzid = settings.getString("web.timezone", null); + String tzid = settings.getString(Keys.web.timezone, null); if (StringUtils.isEmpty(tzid)) { timezone = TimeZone.getDefault(); return timezone; @@ -135,7 +141,7 @@ public class RuntimeManager implements IRuntimeManager { SimpleDateFormat df = new SimpleDateFormat("z Z"); df.setTimeZone(zone); String offset = df.format(new Date()); - logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")"); + logger.info("{}{} ({})", new Object [] { type, zone.getID(), offset }); } /** diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java new file mode 100644 index 00000000..82a8a043 --- /dev/null +++ b/src/main/java/com/gitblit/manager/ServicesManager.java @@ -0,0 +1,188 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants.FederationToken; +import com.gitblit.FederationPullExecutor; +import com.gitblit.Gitblit; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.fanout.FanoutNioService; +import com.gitblit.fanout.FanoutService; +import com.gitblit.fanout.FanoutSocketService; +import com.gitblit.git.GitDaemon; +import com.gitblit.models.FederationModel; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.TimeUtils; + +/** + * Services manager manages long-running services/processes that either have no + * direct relation to other managers OR require really high-level manager + * integration (i.e. a Gitblit instance). + * + * @author James Moger + * + */ +public class ServicesManager implements IServicesManager { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5); + + private final IStoredSettings settings; + + private final Gitblit gitblit; + + private FanoutService fanoutService; + + private GitDaemon gitDaemon; + + public ServicesManager(Gitblit gitblit) { + this.settings = gitblit.getSettings(); + this.gitblit = gitblit; + } + + @Override + public ServicesManager start() { + configureFederation(); + configureFanout(); + configureGitDaemon(); + + return this; + } + + @Override + public ServicesManager stop() { + scheduledExecutor.shutdownNow(); + if (fanoutService != null) { + fanoutService.stop(); + } + if (gitDaemon != null) { + gitDaemon.stop(); + } + return this; + } + + protected void configureFederation() { + boolean validPassphrase = true; + String passphrase = settings.getString(Keys.federation.passphrase, ""); + if (StringUtils.isEmpty(passphrase)) { + logger.info("Federation passphrase is blank! This server can not be PULLED from."); + validPassphrase = false; + } + if (validPassphrase) { + // standard tokens + for (FederationToken tokenType : FederationToken.values()) { + logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(), + gitblit.getFederationToken(tokenType))); + } + + // federation set tokens + for (String set : settings.getStrings(Keys.federation.sets)) { + logger.info(MessageFormat.format("Federation Set {0} token = {1}", set, + gitblit.getFederationToken(set))); + } + } + + // Schedule or run the federation executor + List<FederationModel> registrations = gitblit.getFederationRegistrations(); + if (registrations.size() > 0) { + FederationPuller executor = new FederationPuller(registrations); + scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES); + } + } + + protected void configureGitDaemon() { + int port = settings.getInteger(Keys.git.daemonPort, 0); + String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); + if (port > 0) { + try { + gitDaemon = new GitDaemon(gitblit); + gitDaemon.start(); + } catch (IOException e) { + gitDaemon = null; + logger.error(MessageFormat.format("Failed to start Git Daemon on {0}:{1,number,0}", bindInterface, port), e); + } + } else { + logger.info("Git Daemon is disabled."); + } + } + + protected void configureFanout() { + // startup Fanout PubSub service + if (settings.getInteger(Keys.fanout.port, 0) > 0) { + String bindInterface = settings.getString(Keys.fanout.bindInterface, null); + int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT); + boolean useNio = settings.getBoolean(Keys.fanout.useNio, true); + int limit = settings.getInteger(Keys.fanout.connectionLimit, 0); + + if (useNio) { + if (StringUtils.isEmpty(bindInterface)) { + fanoutService = new FanoutNioService(port); + } else { + fanoutService = new FanoutNioService(bindInterface, port); + } + } else { + if (StringUtils.isEmpty(bindInterface)) { + fanoutService = new FanoutSocketService(port); + } else { + fanoutService = new FanoutSocketService(bindInterface, port); + } + } + + fanoutService.setConcurrentConnectionLimit(limit); + fanoutService.setAllowAllChannelAnnouncements(false); + fanoutService.start(); + } else { + logger.info("Fanout PubSub service is disabled."); + } + } + + private class FederationPuller extends FederationPullExecutor { + + public FederationPuller(FederationModel registration) { + super(Arrays.asList(registration)); + } + + public FederationPuller(List<FederationModel> registrations) { + super(registrations); + } + + @Override + public void reschedule(FederationModel registration) { + // schedule the next pull + int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency); + registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L)); + scheduledExecutor.schedule(new FederationPuller(registration), mins, TimeUnit.MINUTES); + logger.info(MessageFormat.format( + "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}", + registration.name, registration.url, registration.nextPull)); + } + + } +} diff --git a/src/main/java/com/gitblit/manager/SessionManager.java b/src/main/java/com/gitblit/manager/SessionManager.java index e6c3a2d3..6a85da89 100644 --- a/src/main/java/com/gitblit/manager/SessionManager.java +++ b/src/main/java/com/gitblit/manager/SessionManager.java @@ -65,7 +65,7 @@ public class SessionManager implements ISessionManager { } @Override - public IManager setup() { + public SessionManager start() { List<String> services = settings.getStrings("realm.authenticationServices"); for (String service : services) { // TODO populate authentication services here @@ -74,7 +74,7 @@ public class SessionManager implements ISessionManager { } @Override - public IManager stop() { + public SessionManager stop() { return this; } diff --git a/src/main/java/com/gitblit/manager/UserManager.java b/src/main/java/com/gitblit/manager/UserManager.java index f781c4c7..90b9d1e2 100644 --- a/src/main/java/com/gitblit/manager/UserManager.java +++ b/src/main/java/com/gitblit/manager/UserManager.java @@ -64,13 +64,13 @@ public class UserManager implements IUserManager { * @param userService */ public void setUserService(IUserService userService) { - logger.info("Setting up user service " + userService.toString()); + logger.info("UserService: " + userService.toString()); this.userService = userService; this.userService.setup(runtimeManager); } @Override - public IManager setup() { + public UserManager start() { if (this.userService == null) { String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties"); IUserService service = null; @@ -114,7 +114,7 @@ public class UserManager implements IUserManager { } @Override - public IManager stop() { + public UserManager stop() { return this; } diff --git a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java index aaa6c668..8e518de5 100644 --- a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java +++ b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java @@ -137,7 +137,7 @@ public class MockRuntimeManager implements IRuntimeManager { } @Override - public IRuntimeManager setup() { + public IRuntimeManager start() { return this; } } |