package nl.minicom.gitolite.manager.models; import java.util.Map.Entry; import java.util.SortedSet; import nl.minicom.gitolite.manager.exceptions.ModificationException; import nl.minicom.gitolite.manager.models.Recorder.Modification; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; /** * The {@code Config} class is a representation of a configuration of gitolite. * If you wish to change your gitolite configuration, You will have to manipulate this object. * * @author Michael de Jong <<a href="mailto:michaelj@minicom.nl">michaelj@minicom.nl</a>> */ public final class Config { private final SortedSet<Repository> repositories; private final SortedSet<Group> groups; private final SortedSet<User> users; private final Recorder recorder; /** * This constructs a new {@code Config} object. */ Config() { this(new Recorder()); } /** * This constructs a new {@code Config} object. * * @param recorder * The {@link Recorder} to use when modifying this {@code Config} object. */ Config(Recorder recorder) { Preconditions.checkNotNull(recorder); this.recorder = recorder; this.repositories = Sets.newTreeSet(Repository.SORT_BY_NAME); this.groups = Sets.newTreeSet(Group.SORT_BY_NAME); this.users = Sets.newTreeSet(User.SORT_BY_TYPE_AND_NAME); } /** * @return The {@link Recorder} used by this {@code Config} to record changes. */ Recorder getRecorder() { return recorder; } /** * This method ensures that the {@code Config} object will contain a {@link Repository} * with the specified name. This means that if no such {@link Repository} exists, it will * be created. * * @param repoName * The name the {@link Repository} should have. * This may not be NULL or an empty {@link String}. * * @return * The existing or newly created {@link Repository} object. */ public Repository ensureRepositoryExists(String repoName) { validateRepositoryName(repoName); synchronized (repositories) { Repository repository = getRepository(repoName); if (repository == null) { repository = createRepository(repoName); } return repository; } } /** * This method creates a new {@link Repository} with the specified name. * * @param repoName * The name of the {@link Repository}. This may not be NULL or an empty {@link String}. * If the {@link Repository} already exists a {@link IllegalArgumentException} is thrown. * Use the {@link Config#ensureRepositoryExists(String)} method in stead. * * @return * The created {@link Repository} object. */ public Repository createRepository(final String repoName) { validateRepositoryName(repoName); Repository repository = null; synchronized (repositories) { if (getRepository(repoName) != null) { throw new IllegalArgumentException("The repository " + repoName + " has already been created!"); } repository = new Repository(repoName, recorder); repositories.add(repository); } recorder.append(new Modification("Create repository: " + repoName) { @Override public void apply(Config config) throws ModificationException { try { config.createRepository(repoName); } catch (IllegalArgumentException e) { throw new ModificationException(); } } }); return repository; } /** * This method removes the specified {@link Repository} from the {@code Config} object. * * @param repository * The {@link Repository} to remove. This may not be NULL. * * @return * True if it was removed, or false if it was not. * In the latter case it most likely did not exist. */ public boolean removeRepository(Repository repository) { Preconditions.checkNotNull(repository); boolean removed = false; synchronized (repositories) { removed = repositories.remove(repository); } final String repoName = repository.getName(); recorder.append(new Modification("Remove repository: " + repository.getName()) { @Override public void apply(Config config) throws ModificationException { Repository repo = config.getRepository(repoName); if (repo == null) { throw new ModificationException(); } config.removeRepository(repo); } }); return removed; } /** * This method checks if the {@code Config} object contains a {@link Repository} * object with the specified name. * * @param repoName * The name of the {@link Repository} to look for. * This may not be NULL or an empty {@link String}. * * @return * True if a {@link Repository} with the specified name exists, false otherwise. */ public boolean hasRepository(String repoName) { validateRepositoryName(repoName); return getRepository(repoName) != null; } /** * This method fetches the {@link Repository} object with the specified name * from the {@code Config} object. * * @param repoName * The name of the {@link Repository} to look for. * This may not be NULL or an empty {@link String}. * * @return * The {@link Repository} object with the specified name, * or NULL if no such {@link Repository} exists. */ public Repository getRepository(String repoName) { validateRepositoryName(repoName); synchronized (repositories) { for (Repository repository : repositories) { if (repository.getName().equals(repoName)) { return repository; } } } return null; } /** * @return * Am {@link ImmutableSet} of {@link Repository} objects currently * registered in the {@code Config} object. */ public ImmutableSet<Repository> getRepositories() { synchronized (repositories) { return ImmutableSortedSet.copyOf(Repository.SORT_BY_NAME, repositories); } } private void validateRepositoryName(String repoName) { Preconditions.checkNotNull(repoName); Preconditions.checkArgument(!repoName.isEmpty()); } /** * This method ensures that the {@code Config} object will contain a {@link Group} * with the specified name. This means that if no such {@link Group} exists, it will * be created. * * @param groupName * The name the {@link Group} should have. * This may not be NULL, and it must start with the character '@'. * * @return * The existing or newly created {@link Group} object. */ public Group ensureGroupExists(String groupName) { validateGroupName(groupName); synchronized (groups) { Group group = getGroup(groupName); if (group == null) { group = createGroup(groupName); } return group; } } /** * This method creates a new {@link Group} with the specified name. * * @param groupName * The name of the {@link Group}. This may not be NULL, and it must start with the character '@'. * If the {@link Group} already exists an {@link IllegalArgumentException} is thrown. Use the * {@link Config#ensureGroupExists(String)} method in stead. * * @return * The created {@link Group} object. */ public Group createGroup(final String groupName) { validateGroupName(groupName); Group group = null; synchronized (groups) { if (getGroup(groupName) != null) { throw new IllegalArgumentException("The group " + groupName + " has already been created!"); } group = new Group(groupName, recorder); groups.add(group); } recorder.append(new Modification("Creating group: " + groupName) { @Override public void apply(Config config) throws ModificationException { try { config.createGroup(groupName); } catch (IllegalArgumentException e) { throw new ModificationException(); } } }); return group; } /** * This method removes the specified {@link Repository} from the {@link Group} object. * * @param group * The {@link Group} to remove. This may not be NULL. * * @return * True if it was removed, or false if it was not. * In the latter case it most likely did not exist. */ public boolean removeGroup(Group group) { Preconditions.checkNotNull(group); boolean remove = false; synchronized (groups) { remove = groups.remove(group); } if (remove) { synchronized (repositories) { for (Repository repo : repositories) { repo.revokePermissions(group); } } final String groupName = group.getName(); recorder.append(new Modification("Removing group: " + groupName) { @Override public void apply(Config config) throws ModificationException { Group group = config.getGroup(groupName); if (group == null || !config.removeGroup(group)) { throw new ModificationException(); } } }); } return remove; } /** * This method checks if the {@code Config} object contains a {@link Group} * object with the specified name. * * @param groupName * The name of the {@link Group} to look for. * This may not be NULL, and it must start with the character '@'. * * @return * True if a {@link Group} with the specified name exists, false otherwise. */ public boolean hasGroup(String groupName) { validateGroupName(groupName); return getGroup(groupName) != null; } /** * This method fetches the {@link Group} object with the specified name * from the {@code Config} object. * * @param groupName * The name of the {@link Group} to look for. * This may not be NULL, and it must start with the character '@'. * * @return * The {@link Group} object with the specified name, * or NULL if no such {@link Group} exists. */ public Group getGroup(String groupName) { validateGroupName(groupName); synchronized (groups) { for (Group group : groups) { if (group.getName().equals(groupName)) { return group; } } } return null; } /** * @return * Am {@link ImmutableSet} of {@link Group} objects currently * registered in the {@code Config} object. This includes the "@all" {@link Group}. */ public ImmutableSet<Group> getGroups() { synchronized (groups) { return ImmutableSortedSet.copyOf(Group.SORT_BY_NAME, groups); } } private void validateGroupName(String groupName) { Preconditions.checkNotNull(groupName); Preconditions.checkArgument(!groupName.isEmpty()); Preconditions.checkArgument(groupName.startsWith("@")); } /** * This method ensures that the {@code Config} object will contain a {@link User} * with the specified name. This means that if no such {@link User} exists, it will * be created. * * @param userName * The name the {@link User} should have. * This may not be NULL or an empty {@link String}. * * @return * The existing or newly created {@link User} object. */ public User ensureUserExists(String userName) { validateUserName(userName); synchronized (users) { User user = getUser(userName); if (user == null) { user = createUser(userName); } return user; } } /** * This method creates a new {@link User} with the specified name. * * @param userName * The name of the {@link User}. This may not be NULL or an empty {@link String}. * If the {@link User} already exists, a {@link IllegalArgumentException} is thrown. * Use the {@link Config#ensureUserExists(String)} method in stead. * * @return * The created {@link User} object. * * @throws IllegalArgumentException * If the user has already been created, or the given username is null. */ public User createUser(final String userName) { validateUserName(userName); User user = null; synchronized (users) { if (getUser(userName) != null) { throw new IllegalArgumentException("The user " + userName + " has already been created!"); } user = new User(userName, recorder); users.add(user); } recorder.append(new Modification("Creating user: " + userName) { @Override public void apply(Config config) throws ModificationException { try { config.createUser(userName); } catch (IllegalArgumentException e) { throw new ModificationException(); } } }); return user; } /** * This method removes the specified {@link Repository} from the {@link User} object. * * @param user * The {@link User} to remove. This may not be NULL. * * @return * True if it was removed, or false if it was not. * In the latter case it most likely did not exist. */ public boolean removeUser(User user) { Preconditions.checkNotNull(user); boolean success = false; synchronized (users) { success = users.remove(user); } if (success) { synchronized (repositories) { for (Repository repo : repositories) { repo.revokePermissions(user); } } final String userName = user.getName(); recorder.append(new Modification("Removing user: " + userName) { @Override public void apply(Config config) throws ModificationException { User user = config.getUser(userName); if (user == null || !config.removeUser(user)) { throw new ModificationException(); } } }); } return success; } /** * This method checks if the {@code Config} object contains a {@link User} * object with the specified name. * * @param userName * The name of the {@link User} to look for. * This may not be NULL or an empty {@link String}. * * @return * True if a {@link User} with the specified name exists, false otherwise. */ public boolean hasUser(String userName) { validateUserName(userName); return getUser(userName) != null; } /** * This method fetches the {@link User} object with the specified name * from the {@code Config} object. * * @param userName * The name of the {@link User} to look for. * This may not be NULL or an empty {@link String}. * * @return * The {@link User} object with the specified name, * or NULL if no such {@link User} exists. */ public User getUser(String userName) { validateUserName(userName); synchronized (users) { for (User user : users) { if (user.getName().equals(userName)) { return user; } } } return null; } /** * @return * Am {@link ImmutableSet} of {@link User} objects currently * registered in the {@code Config} object. */ public ImmutableSet<User> getUsers() { synchronized (users) { return ImmutableSortedSet.copyOf(User.SORT_BY_TYPE_AND_NAME, users); } } private void validateUserName(String userName) { Preconditions.checkNotNull(userName); Preconditions.checkArgument(!userName.isEmpty()); } /** * @return The created deep copy of this {@code Config} object. */ Config copy() { Config config = new Config(); // Add users. for (User user : getUsers()) { User created = config.createUser(user.getName()); for (Entry<String, String> entry : user.getKeys().entrySet()) { created.setKey(entry.getKey(), entry.getValue()); } } // Add groups and their users. for (Group group : getGroups()) { Group created = config.createGroup(group.getName()); for (User member : group.getUsers()) { created.add(member); } } // Add sub groups to groups. for (Group group : getGroups()) { Group existing = config.getGroup(group.getName()); for (Group member : group.getGroups()) { existing.add(config.getGroup(member.getName())); } } // Add repositories. for (Repository repo : getRepositories()) { Repository created = config.createRepository(repo.getName()); for (Entry<Permission, Identifiable> entry : repo.getPermissions().entries()) { if (entry.getValue() instanceof User) { User user = config.getUser(entry.getValue().getName()); created.setPermission(user, entry.getKey()); } else { Group group = config.getGroup(entry.getValue().getName()); created.setPermission(group, entry.getKey()); } } } return config; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Config config = (Config) o; if (repositories != null ? !repositories.equals(config.repositories) : config.repositories != null) return false; if (groups != null ? !groups.equals(config.groups) : config.groups != null) return false; return !(users != null ? !users.equals(config.users) : config.users != null); } @Override public int hashCode() { int result = repositories != null ? repositories.hashCode() : 0; result = 31 * result + (groups != null ? groups.hashCode() : 0); result = 31 * result + (users != null ? users.hashCode() : 0); return result; } }