From 55668fa50fd8bf2b9d5c352852855d594ec5905c Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 1 Jul 2018 09:00:54 +0200 Subject: [PATCH 01/19] Added google-http-client dependency --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 1d8e0c8..b56e29f 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,11 @@ 16.0.2 compile + + com.google.http-client + google-http-client + 1.23.0 + \ No newline at end of file From ac4018f6bad536a49d1d9a541299776860da6c45 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 11 Aug 2018 23:08:36 +0200 Subject: [PATCH 02/19] Added initializer. --- pom.xml | 6 +- src/main/java/nl/andrewlalis/Main.java | 10 +- .../nl/andrewlalis/git_api/Initializer.java | 59 ++++++++ .../nl/andrewlalis/git_api/URLBuilder.java | 140 ++++++++++++++++++ src/main/java/nl/andrewlalis/model/Team.java | 5 + 5 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/git_api/Initializer.java create mode 100644 src/main/java/nl/andrewlalis/git_api/URLBuilder.java diff --git a/pom.xml b/pom.xml index b56e29f..597b3c5 100644 --- a/pom.xml +++ b/pom.xml @@ -44,9 +44,9 @@ compile - com.google.http-client - google-http-client - 1.23.0 + org.apache.httpcomponents + httpclient + RELEASE diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 3aa4015..7bcc5fa 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,5 +1,6 @@ package nl.andrewlalis; +import nl.andrewlalis.git_api.Initializer; import nl.andrewlalis.model.Team; import nl.andrewlalis.util.Logging; import nl.andrewlalis.util.TeamGenerator; @@ -33,7 +34,7 @@ public class Main { logger.info("Initializer for Github Repositories in Educational Organizations."); // Get teams from CSV file. - List teams; + List teams = null; try { teams = TeamGenerator.generateFromCSV( userOptions.get("input"), @@ -45,7 +46,12 @@ public class Main { System.exit(1); } - + Initializer initializer = new Initializer( + userOptions.get("organization"), + userOptions.get("token"), + "assignments" + ); + initializer.initializeGithubRepos(teams); } } diff --git a/src/main/java/nl/andrewlalis/git_api/Initializer.java b/src/main/java/nl/andrewlalis/git_api/Initializer.java new file mode 100644 index 0000000..41d5c9d --- /dev/null +++ b/src/main/java/nl/andrewlalis/git_api/Initializer.java @@ -0,0 +1,59 @@ +package nl.andrewlalis.git_api; + +import nl.andrewlalis.model.Team; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc. + */ +public class Initializer { + + private String organizationName; + + private String accessToken; + + private URLBuilder urlBuilder; + + private String assignmentsRepo; + + public Initializer(String organizationName, String accessToken, String assignmentsRepo) { + this.organizationName = organizationName; + this.accessToken = accessToken; + this.assignmentsRepo = assignmentsRepo; + this.urlBuilder = new URLBuilder(organizationName, accessToken); + } + + /** + * Initializes the github repository for all teams given. + * + * Creates for each team: + * - a repository + * - protected master branch + * - development branch + * - adds students to repository + * - creates assignments repository + * - adds all students to assignments repository. + * @param teams The list of student teams. + */ + public void initializeGithubRepos(List teams) { + listMemberTeams(); + } + + private Map listMemberTeams() { + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet get = new HttpGet(this.urlBuilder.buildTeamURL()); + try (CloseableHttpResponse response = client.execute(get)) { + System.out.println(response.getStatusLine()); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/nl/andrewlalis/git_api/URLBuilder.java b/src/main/java/nl/andrewlalis/git_api/URLBuilder.java new file mode 100644 index 0000000..f83cbc1 --- /dev/null +++ b/src/main/java/nl/andrewlalis/git_api/URLBuilder.java @@ -0,0 +1,140 @@ +package nl.andrewlalis.git_api; + +public class URLBuilder { + + /** + * The main URL from which all requests must be formed. + */ + private static final String baseURL = "https://api.github.com"; + + /** + * The name of the github organization in which to create repositories. + */ + private String organizationName; + + /** + * The token needed to use the API. + */ + private String accessToken; + + public URLBuilder(String organizationName, String accessToken) { + this.organizationName = organizationName; + this.accessToken = accessToken; + } + + /** + * @return The URL for adding a repository. + */ + public String buildRepoURL() { + return baseURL + + "/orgs/" + + this.organizationName + + "/repos?access_token=" + + this.accessToken; + } + + /** + * @param repoName The name of the repository. + * @param branch The name of the branch in the repository above. + * @return The URL for setting branch protection. + */ + public String buildBranchProtectionURL(String repoName, String branch) { + return baseURL + + "/repos/" + + this.organizationName + + '/' + + repoName + + "/branches/" + + branch + + "/protection?access_token=" + + this.accessToken; + } + + /** + * @param repoName The name of the repository the branch is in. + * @return The URL for getting a branch reference. + */ + public String buildReferenceGetURL(String repoName) { + return baseURL + + "/repos/" + + this.organizationName + + '/' + + repoName + + "/git/refs/heads/master?acces_token=" + + this.accessToken; + } + + /** + * @param repoName The repository name. + * @return The URL for creating a new branch, once a reference has been obtained. + */ + public String buildReferencePostURL(String repoName) { + return baseURL + + "/repos/" + + this.organizationName + + '/' + + repoName + + "/git/refs?access_token=" + + this.accessToken; + } + + /** + * @param repoName The name of the repository. + * @param collaborator The collaborator's name. + * @return The URL for adding a collaborator to a repository. + */ + public String buildCollaboratorURL(String repoName, String collaborator) { + return baseURL + + "/repos/" + + this.organizationName + + '/' + + repoName + + "/collaborators/" + + collaborator + + "?access_token=" + + this.accessToken; + } + + /** + * @return The URL for obtaining the teams. + */ + public String buildTeamURL() { + return baseURL + + "/orgs/" + + this.organizationName + + "/teams?access_token=" + + this.accessToken; + } + + /** + * @param repoName The name of the repository. + * @param teamName The name of the team to set permissions for. + * @return The URL for setting team permissions of a repository. + */ + public String buildTeamPermissionsURL(String repoName, String teamName) { + return baseURL + + "/teams/" + + teamName + + "/repos/" + + this.organizationName + + '/' + + repoName + + "?access_token=" + + this.accessToken; + } + + /** + * @param repoName The name of the repository. + * @return The URL for archiving a repository. + */ + public String buildArchiveRepoURL(String repoName) { + return baseURL + + "/repos/" + + this.organizationName + + '/' + + repoName + + "?access_token=" + + this.accessToken; + } + +} diff --git a/src/main/java/nl/andrewlalis/model/Team.java b/src/main/java/nl/andrewlalis/model/Team.java index bd82bd2..5716eef 100644 --- a/src/main/java/nl/andrewlalis/model/Team.java +++ b/src/main/java/nl/andrewlalis/model/Team.java @@ -111,6 +111,11 @@ public class Team { return sb.toString(); } + /** + * Returns a pretty formatting of this team so that it can be viewed in the command line. This is mainly for + * debugging purposes. + * @return A string representing the team. + */ @Override public String toString() { StringBuilder sb = new StringBuilder("Team: "); From 6394e9d1d45de707be06feb55b592a83a1dc37ac Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 12 Aug 2018 09:54:10 +0200 Subject: [PATCH 03/19] Added jackson dependency, added TATeam and teaching assistant object, and refactored object hierarchy for model. --- pom.xml | 19 ++++ src/main/java/nl/andrewlalis/Main.java | 14 +-- .../nl/andrewlalis/git_api/Initializer.java | 81 +++++++++++++++-- .../nl/andrewlalis/git_api/URLBuilder.java | 2 +- .../java/nl/andrewlalis/model/Person.java | 89 +++++++++++++++++++ .../java/nl/andrewlalis/model/Student.java | 71 +++------------ .../model/{Team.java => StudentTeam.java} | 10 +-- .../java/nl/andrewlalis/model/TATeam.java | 21 +++++ .../andrewlalis/model/TeachingAssistant.java | 16 ++++ .../nl/andrewlalis/util/TeamGenerator.java | 36 ++++---- 10 files changed, 265 insertions(+), 94 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/model/Person.java rename src/main/java/nl/andrewlalis/model/{Team.java => StudentTeam.java} (95%) create mode 100644 src/main/java/nl/andrewlalis/model/TATeam.java create mode 100644 src/main/java/nl/andrewlalis/model/TeachingAssistant.java diff --git a/pom.xml b/pom.xml index 597b3c5..4114823 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,10 @@ jar + + 2.9.6 + + org.apache.commons @@ -48,6 +52,21 @@ httpclient RELEASE + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 7bcc5fa..9938851 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,7 +1,7 @@ package nl.andrewlalis; import nl.andrewlalis.git_api.Initializer; -import nl.andrewlalis.model.Team; +import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.util.Logging; import nl.andrewlalis.util.TeamGenerator; @@ -33,16 +33,16 @@ public class Main { logger.info("Initializer for Github Repositories in Educational Organizations."); - // Get teams from CSV file. - List teams = null; + // Get studentTeams from CSV file. + List studentTeams = null; try { - teams = TeamGenerator.generateFromCSV( + studentTeams = TeamGenerator.generateFromCSV( userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize")) ); - logger.info("Teams created: " + teams); + logger.info("Teams created: " + studentTeams); } catch (IOException | ArrayIndexOutOfBoundsException e) { - logger.severe("Unable to generate teams from CSV file, exiting."); + logger.severe("Unable to generate studentTeams from CSV file, exiting."); System.exit(1); } @@ -51,7 +51,7 @@ public class Main { userOptions.get("token"), "assignments" ); - initializer.initializeGithubRepos(teams); + initializer.initializeGithubRepos(studentTeams); } } diff --git a/src/main/java/nl/andrewlalis/git_api/Initializer.java b/src/main/java/nl/andrewlalis/git_api/Initializer.java index 41d5c9d..263e087 100644 --- a/src/main/java/nl/andrewlalis/git_api/Initializer.java +++ b/src/main/java/nl/andrewlalis/git_api/Initializer.java @@ -1,28 +1,62 @@ package nl.andrewlalis.git_api; -import nl.andrewlalis.model.Team; +import com.fasterxml.jackson.databind.ObjectMapper; +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.TATeam; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.logging.Logger; /** * This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc. */ public class Initializer { + /** + * The name of the organization to operate on. + */ private String organizationName; + /** + * The oauth access token needed for the request. + */ private String accessToken; + /** + * The object which simplifies creating REST URL's for most of the requests. + */ private URLBuilder urlBuilder; + /** + * The name of the assignments repository where students will get assignments from. + */ private String assignmentsRepo; + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(Initializer.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + public Initializer(String organizationName, String accessToken, String assignmentsRepo) { this.organizationName = organizationName; this.accessToken = accessToken; @@ -31,26 +65,61 @@ public class Initializer { } /** - * Initializes the github repository for all teams given. + * Initializes the github repository for all studentTeams given. * + * Creates for the entire organization: + * - an assignments repository with protected master branch and TA permissions. * Creates for each team: * - a repository * - protected master branch * - development branch * - adds students to repository - * - creates assignments repository * - adds all students to assignments repository. - * @param teams The list of student teams. + * @param studentTeams The list of student studentTeams. */ - public void initializeGithubRepos(List teams) { + public void initializeGithubRepos(List studentTeams) { listMemberTeams(); + //this.createRepository(this.assignmentsRepo, ) } + private boolean createRepository(String repositoryName, TATeam taTeam) { + List nvps = new ArrayList(); + nvps.add(new BasicNameValuePair("name", repositoryName)); + nvps.add(new BasicNameValuePair("private", "True")); + nvps.add(new BasicNameValuePair("has_issues", "True")); + nvps.add(new BasicNameValuePair("has_wiki", "True")); + nvps.add(new BasicNameValuePair("team_id", taTeam.getId())); + nvps.add(new BasicNameValuePair("auto_init", "False")); + nvps.add(new BasicNameValuePair("gitignore_template", "Java")); + HttpClient client = HttpClientBuilder.create().build(); + HttpPost post = new HttpPost(this.urlBuilder.buildRepoURL()); + try { + post.setEntity(new StringEntity(nvps.toString())); + post.setHeader("Content-type", "application/json"); + HttpResponse response = client.execute(post); + + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + /** + * Obtains a list of all teams in the organization, meaning all teaching assistant teams. + * @return A list of all teams in the organization. + */ private Map listMemberTeams() { CloseableHttpClient client = HttpClients.createDefault(); HttpGet get = new HttpGet(this.urlBuilder.buildTeamURL()); try (CloseableHttpResponse response = client.execute(get)) { - System.out.println(response.getStatusLine()); + StatusLine line = response.getStatusLine(); + logger.finest("Response code from listing member teams: " + line.getStatusCode()); + logger.finest("Response entity: " + response.getEntity().toString()); + HashMap result = + new ObjectMapper().readValue(response.getEntity().getContent(), HashMap.class); + for (Map.Entry e : result.entrySet()) { + logger.finest(e.getKey() + ": " + e.getValue()); + } } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/nl/andrewlalis/git_api/URLBuilder.java b/src/main/java/nl/andrewlalis/git_api/URLBuilder.java index f83cbc1..5966224 100644 --- a/src/main/java/nl/andrewlalis/git_api/URLBuilder.java +++ b/src/main/java/nl/andrewlalis/git_api/URLBuilder.java @@ -60,7 +60,7 @@ public class URLBuilder { + this.organizationName + '/' + repoName - + "/git/refs/heads/master?acces_token=" + + "/git/refs/heads/master?access_token=" + this.accessToken; } diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java new file mode 100644 index 0000000..ea12b84 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -0,0 +1,89 @@ +package nl.andrewlalis.model; + +/** + * A generic object that students, teaching assistants, and professors can extend from. This covers all the basic + * functionality that applies to anyone in the system. + */ +public abstract class Person { + + /** + * The unique identification number for this person. (P- or S-Number) + */ + protected int number; + + /** + * The person's first and last name. + */ + protected String name; + + /** + * The person's email address. + */ + protected String emailAddress; + + /** + * The person's github username. + */ + protected String githubUsername; + + /** + * Constructs a Person from all the basic information needed. + * @param number Either an S- or P-Number without the letter prefix. + * @param name The first, middle (if applicable) and last name. + * @param emailAddress The email address. (Either university or personal.) + * @param githubUsername The person's github username. + */ + public Person(int number, String name, String emailAddress, String githubUsername){ + this.number = number; + this.name = name; + this.emailAddress = emailAddress; + this.githubUsername = githubUsername; + } + + /** + * Accessors + */ + public int getNumber(){ + return this.number; + } + + public String getName(){ + return this.name; + } + + public String getEmailAddress(){ + return this.emailAddress; + } + + public String getGithubUsername(){ + return this.githubUsername; + } + + /** + * Determines if two persons are the same. This is defined as: + * Two persons are equal if at least one of their personal data points is identical. Because each of the data points + * should be unique to this person alone, if there is any conflict, assume that they are equal. + * @param o The object to compare to. + * @return True if the two persons share personal data, or false if all data is unique among the two. + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Person)) { + return false; + } + Person p = (Person)o; + return p.getNumber() == this.getNumber() + || p.getEmailAddress().equals(this.getEmailAddress()) + || p.getGithubUsername().equals(this.getGithubUsername()) + || p.getName().equalsIgnoreCase(this.getName()); + } + + /** + * Represents the person as a basic comma-separated string object. + * @return A comma-separated String object. + */ + @Override + public String toString() { + return this.getName() + ", " + this.getNumber() + ", " + this.getEmailAddress() + ", " + this.getGithubUsername(); + } +} diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index d268ab5..1cb0474 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -1,66 +1,34 @@ package nl.andrewlalis.model; -import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Represents one student's github information. */ -public class Student { - - /** - * The student's S-number. - */ - private int number; - - /** - * The student's name. - */ - private String name; - - /** - * The student's email. - */ - private String emailAddress; - - /** - * The student's github username. - */ - private String githubUsername; +public class Student extends Person { /** * A list of partners that the student has said that they would like to be partners with. */ private List preferredPartners; + /** + * Constructs a student similarly to a Person, but with an extra preferredPartners list. + * @param number The student's S-Number. + * @param name The student's name. + * @param emailAddress The student's email address. + * @param githubUsername The student's github username. + * @param preferredPartners A list of this student's preferred partners, as a list of integers representing the + * other students' numbers. + */ public Student(int number, String name, String emailAddress, String githubUsername, List preferredPartners) { - this.number = number; - this.name = name; - this.emailAddress = emailAddress; - this.githubUsername = githubUsername; + super(number, name, emailAddress, githubUsername); this.preferredPartners = preferredPartners; } - @Override - public String toString() { - return this.number + " - " + this.name + " - " + this.emailAddress + " - " + this.githubUsername; - } - - public int getNumber() { - return number; - } - - public String getEmailAddress() { - return emailAddress; - } - - public String getGithubUsername() { - return githubUsername; - } - public List getPreferredPartners() { - return preferredPartners; + return this.preferredPartners; } /** @@ -68,23 +36,12 @@ public class Student { * @param studentMap A mapping from student number to student for all students who have signed up. * @return A team with unknown id, comprised of this student's preferred partners. */ - public Team getPreferredTeam(Map studentMap) { - Team t = new Team(); + public StudentTeam getPreferredTeam(Map studentMap) { + StudentTeam t = new StudentTeam(); for (int partnerNumber : this.getPreferredPartners()) { t.addStudent(studentMap.get(partnerNumber)); } t.addStudent(this); return t; } - - @Override - public boolean equals(Object s) { - if (!(s instanceof Student)) { - return false; - } - Student student = (Student) s; - return student.getNumber() == this.getNumber() - || student.getEmailAddress().equals(this.getEmailAddress()) - || student.getGithubUsername().equals(this.getGithubUsername()); - } } diff --git a/src/main/java/nl/andrewlalis/model/Team.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java similarity index 95% rename from src/main/java/nl/andrewlalis/model/Team.java rename to src/main/java/nl/andrewlalis/model/StudentTeam.java index 5716eef..d335c80 100644 --- a/src/main/java/nl/andrewlalis/model/Team.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -6,7 +6,7 @@ import java.util.List; /** * Represents one or more students' collective information. */ -public class Team { +public class StudentTeam { /** * The list of students in this team. @@ -18,7 +18,7 @@ public class Team { */ private int id; - public Team() { + public StudentTeam() { this.students = new ArrayList<>(); this.id = -1; } @@ -118,7 +118,7 @@ public class Team { */ @Override public String toString() { - StringBuilder sb = new StringBuilder("Team: "); + StringBuilder sb = new StringBuilder("StudentTeam: "); sb.append(this.id).append('\n'); for (Student s : this.students) { sb.append('\t').append(s.toString()).append('\n'); @@ -134,8 +134,8 @@ public class Team { */ @Override public boolean equals(Object o) { - if (o instanceof Team) { - Team t = (Team) o; + if (o instanceof StudentTeam) { + StudentTeam t = (StudentTeam) o; if (t.getStudentCount() != this.getStudentCount()) { return false; } diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java new file mode 100644 index 0000000..8d96e67 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -0,0 +1,21 @@ +package nl.andrewlalis.model; + +import java.util.List; + +public class TATeam { + + private List teachingAssistants; + + public TATeam(List teachingAssistants) { + this.teachingAssistants = teachingAssistants; + } + + /** + * Gets the unique identification for this TA team. + * @return A string representing the id of this team. + */ + public String getId() { + return null; + } + +} diff --git a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java new file mode 100644 index 0000000..918bb5c --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java @@ -0,0 +1,16 @@ +package nl.andrewlalis.model; + +public class TeachingAssistant extends Person { + + /** + * Constructs a Teaching Assistant from all the basic information needed, much like its parent, Person. + * + * @param number Either an S- or P-Number without the letter prefix. + * @param name The first, middle (if applicable) and last name. + * @param emailAddress The email address. (Either university or personal.) + * @param githubUsername The person's github username. + */ + public TeachingAssistant(int number, String name, String emailAddress, String githubUsername) { + super(number, name, emailAddress, githubUsername); + } +} diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index fb99e71..16316d7 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -1,7 +1,7 @@ package nl.andrewlalis.util; import nl.andrewlalis.model.Student; -import nl.andrewlalis.model.Team; +import nl.andrewlalis.model.StudentTeam; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; @@ -32,11 +32,11 @@ public class TeamGenerator { * @throws IOException If the file is unable to be read. * @throws IllegalArgumentException If an invalid teamsize is given. */ - public static List generateFromCSV(String filename, int teamSize) throws IOException, IllegalArgumentException { + public static List generateFromCSV(String filename, int teamSize) throws IOException, IllegalArgumentException { logger.info("Generating teams of size " + teamSize); if (teamSize < 1) { logger.severe("Invalid team size."); - throw new IllegalArgumentException("Team size must be greater than or equal to 1. Got " + teamSize); + throw new IllegalArgumentException("StudentTeam size must be greater than or equal to 1. Got " + teamSize); } logger.fine("Parsing CSV file."); Iterable records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename)); @@ -46,8 +46,8 @@ public class TeamGenerator { try { studentMap = readAllStudents(records, teamSize); } catch (ArrayIndexOutOfBoundsException e) { - logger.severe("Team size does not match column count in records."); - throw new IllegalArgumentException("Team size does not match column count in records."); + logger.severe("StudentTeam size does not match column count in records."); + throw new IllegalArgumentException("StudentTeam size does not match column count in records."); } @@ -72,28 +72,28 @@ public class TeamGenerator { * @param teamSize The preferred maximum size for a team. * @return A list of teams, most of which are of teamSize size. */ - private static List generateAllValidTeams(Map studentMap, int teamSize) { + private static List generateAllValidTeams(Map studentMap, int teamSize) { List singleStudents = new ArrayList<>(studentMap.values()); - List teams = new ArrayList<>(); + List studentTeams = new ArrayList<>(); int teamCount = 1; // For each student, try to make a team from its preferred partners. for (Map.Entry e : studentMap.entrySet()) { - Team t = e.getValue().getPreferredTeam(studentMap); + StudentTeam t = e.getValue().getPreferredTeam(studentMap); logger.finest("Checking if student's preferred team is valid: " + t.getStudents()); // Check if the team is of a valid size, and is not a duplicate. - // Note that at this stage, singles are treated as teams of 1, and thus not valid for any teamSize > 1. - if (t.isValid(teamSize) && !teams.contains(t)) { + // Note that at this stage, singles are treated as studentTeams of 1, and thus not valid for any teamSize > 1. + if (t.isValid(teamSize) && !studentTeams.contains(t)) { // Once we know this team is completely valid, we remove all the students in it from the list of singles. t.setId(teamCount++); singleStudents.removeAll(t.getStudents()); - teams.add(t); + studentTeams.add(t); logger.fine("Created team: " + t); } } - teams.addAll(mergeSingleStudents(singleStudents, teamSize, teamCount)); - return teams; + studentTeams.addAll(mergeSingleStudents(singleStudents, teamSize, teamCount)); + return studentTeams; } /** @@ -104,10 +104,10 @@ public class TeamGenerator { * @param teamIndex The current number used in assigning an id to the team. * @return A list of teams comprising of single students. */ - private static List mergeSingleStudents(List singleStudents, int teamSize, int teamIndex) { - List teams = new ArrayList<>(); + private static List mergeSingleStudents(List singleStudents, int teamSize, int teamIndex) { + List studentTeams = new ArrayList<>(); while (!singleStudents.isEmpty()) { - Team t = new Team(); + StudentTeam t = new StudentTeam(); t.setId(teamIndex++); logger.fine("Creating new team of single students: " + t); while (t.getStudentCount() < teamSize && !singleStudents.isEmpty()) { @@ -115,10 +115,10 @@ public class TeamGenerator { logger.finest("Single student: " + s); t.addStudent(s); } - teams.add(t); + studentTeams.add(t); logger.fine("Created team: " + t); } - return teams; + return studentTeams; } /** From 7e9a027a4374dce5e094dc92d9ef07e21deffbad Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 12 Aug 2018 11:28:40 +0200 Subject: [PATCH 04/19] Can create assignments repository now. --- src/main/java/nl/andrewlalis/Main.java | 3 +- .../nl/andrewlalis/git_api/Initializer.java | 91 ++++++++++++++----- .../java/nl/andrewlalis/model/TATeam.java | 52 ++++++++++- 3 files changed, 119 insertions(+), 27 deletions(-) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 9938851..9ba5336 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -8,6 +8,7 @@ import nl.andrewlalis.util.TeamGenerator; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.logging.Level; import java.util.logging.Logger; import nl.andrewlalis.util.CommandLine; @@ -49,7 +50,7 @@ public class Main { Initializer initializer = new Initializer( userOptions.get("organization"), userOptions.get("token"), - "assignments" + "assignments_2018" ); initializer.initializeGithubRepos(studentTeams); diff --git a/src/main/java/nl/andrewlalis/git_api/Initializer.java b/src/main/java/nl/andrewlalis/git_api/Initializer.java index 263e087..7fddae0 100644 --- a/src/main/java/nl/andrewlalis/git_api/Initializer.java +++ b/src/main/java/nl/andrewlalis/git_api/Initializer.java @@ -1,6 +1,9 @@ package nl.andrewlalis.git_api; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.model.TATeam; import org.apache.http.HttpResponse; @@ -16,13 +19,13 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; +import java.io.BufferedReader; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.io.InputStreamReader; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc. @@ -78,51 +81,95 @@ public class Initializer { * @param studentTeams The list of student studentTeams. */ public void initializeGithubRepos(List studentTeams) { - listMemberTeams(); - //this.createRepository(this.assignmentsRepo, ) + List taTeams = listMemberTeams(); + TATeam teamAll = this.getTeamAll(taTeams, "teaching-assistants"); + this.createRepository(this.assignmentsRepo, teamAll); } private boolean createRepository(String repositoryName, TATeam taTeam) { - List nvps = new ArrayList(); - nvps.add(new BasicNameValuePair("name", repositoryName)); - nvps.add(new BasicNameValuePair("private", "True")); - nvps.add(new BasicNameValuePair("has_issues", "True")); - nvps.add(new BasicNameValuePair("has_wiki", "True")); - nvps.add(new BasicNameValuePair("team_id", taTeam.getId())); - nvps.add(new BasicNameValuePair("auto_init", "False")); - nvps.add(new BasicNameValuePair("gitignore_template", "Java")); + logger.info("Creating repository: " + repositoryName); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = mapper.createObjectNode(); + root.put("name", repositoryName); + root.put("private", false); + root.put("has_issues", true); + root.put("has_wiki", true); + root.put("team_id", taTeam.getId()); + root.put("auto_init", false); + root.put("gitignore_template", "Java"); + String json = null; + try { + json = mapper.writer().writeValueAsString(root); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + HttpClient client = HttpClientBuilder.create().build(); HttpPost post = new HttpPost(this.urlBuilder.buildRepoURL()); try { - post.setEntity(new StringEntity(nvps.toString())); + post.setEntity(new StringEntity(json)); post.setHeader("Content-type", "application/json"); HttpResponse response = client.execute(post); - + logger.info("Response from creating repository POST: " + response.getStatusLine().getStatusCode()); + logger.info(getResponseBody(response)); } catch (IOException e) { e.printStackTrace(); } return false; } + /** + * Extracts the team containing all TA's from a list of all teams. + * @param taTeams The list of all teams in the organization. + * @param teamAllName The name of the team representing all TA's. + * @return The team which holds all TA's. + */ + private TATeam getTeamAll(List taTeams, String teamAllName) { + for (TATeam team : taTeams) { + if (team.getName().equals(teamAllName)) { + return team; + } + } + return null; + } + /** * Obtains a list of all teams in the organization, meaning all teaching assistant teams. * @return A list of all teams in the organization. */ - private Map listMemberTeams() { + private List listMemberTeams() { CloseableHttpClient client = HttpClients.createDefault(); HttpGet get = new HttpGet(this.urlBuilder.buildTeamURL()); try (CloseableHttpResponse response = client.execute(get)) { StatusLine line = response.getStatusLine(); - logger.finest("Response code from listing member teams: " + line.getStatusCode()); - logger.finest("Response entity: " + response.getEntity().toString()); - HashMap result = - new ObjectMapper().readValue(response.getEntity().getContent(), HashMap.class); - for (Map.Entry e : result.entrySet()) { - logger.finest(e.getKey() + ": " + e.getValue()); + logger.finer("Response code from listing member teams: " + line.getStatusCode()); + String result = getResponseBody(response); + logger.finer("Response entity: " + result); + + ObjectMapper mapper = new ObjectMapper(); + List memberTeams = mapper.readValue(result, new TypeReference>(){}); + for (TATeam r : memberTeams) { + logger.finest(r.toString()); } + return memberTeams; } catch (IOException e) { e.printStackTrace(); } return null; } + + /** + * Extracts a string which represents the response body of an HttpResponse. + * @param response The response to a previous request. + * @return A string containing the whole response body. + */ + private static String getResponseBody(HttpResponse response) { + try { + return new BufferedReader(new InputStreamReader(response.getEntity().getContent())).lines().collect(Collectors.joining("\n")); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } } diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 8d96e67..572b77a 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -1,21 +1,65 @@ package nl.andrewlalis.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; import java.util.List; +/** + * Represents a teaching assistant team, which is itself a 'team' in the organization. This class is used for parsing + * json from requests to github to get a list of all teams in the organization. + */ +@JsonIgnoreProperties(ignoreUnknown = true) public class TATeam { private List teachingAssistants; - public TATeam(List teachingAssistants) { + /** + * The team's display name. + */ + @JsonProperty("name") + private String name; + + /** + * The team's unique identifier. + */ + @JsonProperty("id") + private int id; + + /** + * Constructs a team without any teaching assistant members. + * @param name The name of the team. + * @param id The unique identifier for this team. + */ + @JsonCreator + public TATeam(@JsonProperty("name") String name, @JsonProperty("id") int id) { + this.name = name; + this.id = id; + this.teachingAssistants = new ArrayList(); + } + + /** + * Constructs a team with a list of teaching assistants that are part of it. + * @param teachingAssistants The list of teaching assistants that are part of the team. + */ + public TATeam(List teachingAssistants, String name, int id) { this.teachingAssistants = teachingAssistants; + this.name = name; + this.id = id; } /** * Gets the unique identification for this TA team. - * @return A string representing the id of this team. + * @return An integer representing the id of this team. */ - public String getId() { - return null; + public int getId() { + return this.id; + } + + public String getName() { + return this.name; } } From 646efaf06c9fcfb21ea32b6fbf7d5e73b81cfec5 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 12 Aug 2018 22:07:45 +0200 Subject: [PATCH 05/19] Implemented all other initializer methods, and implemented some basic logging reporting. Still need to make a way to handle accumulated errors. --- src/main/java/nl/andrewlalis/Main.java | 47 ++- .../nl/andrewlalis/git_api/Initializer.java | 334 ++++++++++++++++-- .../nl/andrewlalis/util/TeamGenerator.java | 14 +- 3 files changed, 346 insertions(+), 49 deletions(-) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 9ba5336..6fb18d1 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -8,6 +8,7 @@ import nl.andrewlalis.util.TeamGenerator; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,6 +27,9 @@ public class Main { Map userOptions = CommandLine.parseArgs(args); // Initialize logger. + ConsoleHandler handler = new ConsoleHandler(); + handler.setLevel(Level.FINEST); + logger.addHandler(handler); try { Logging.setup(true); // TODO: Replace true with command line arg. } catch (IOException e) { @@ -35,24 +39,41 @@ public class Main { logger.info("Initializer for Github Repositories in Educational Organizations."); // Get studentTeams from CSV file. - List studentTeams = null; - try { - studentTeams = TeamGenerator.generateFromCSV( - userOptions.get("input"), - Integer.parseInt(userOptions.get("teamsize")) - ); - logger.info("Teams created: " + studentTeams); - } catch (IOException | ArrayIndexOutOfBoundsException e) { - logger.severe("Unable to generate studentTeams from CSV file, exiting."); - System.exit(1); - } + List studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize"))); Initializer initializer = new Initializer( userOptions.get("organization"), userOptions.get("token"), - "assignments_2018" + "assignments_2018", + "teaching-assistants", + "advoop_2018" ); - initializer.initializeGithubRepos(studentTeams); + + try { + initializer.initializeGithubRepos(studentTeams); + } catch (Exception e) { + e.printStackTrace(); + } } + + /** + * Reads a list of students from a CSV file and compiles a list of teams based on their preferred partners. + * @param filename The name of the CSV file. + * @param teamSize The intended size of teams. + * @return A list of student teams. + */ + private static List getStudentTeamsFromCSV(String filename, int teamSize) { + List studentTeams = null; + try { + studentTeams = TeamGenerator.generateFromCSV(filename, teamSize); + logger.info("Teams created: " + studentTeams); + return studentTeams; + } catch (IOException | ArrayIndexOutOfBoundsException e) { + logger.severe("Unable to generate studentTeams from CSV file, exiting."); + System.exit(1); + return null; + } + } + } diff --git a/src/main/java/nl/andrewlalis/git_api/Initializer.java b/src/main/java/nl/andrewlalis/git_api/Initializer.java index 7fddae0..086c6a6 100644 --- a/src/main/java/nl/andrewlalis/git_api/Initializer.java +++ b/src/main/java/nl/andrewlalis/git_api/Initializer.java @@ -3,27 +3,30 @@ package nl.andrewlalis.git_api; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.model.TATeam; import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.util.ArrayList; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -52,6 +55,27 @@ public class Initializer { */ private String assignmentsRepo; + /** + * The name of the team which contains all teaching assistants. + */ + private String teachingAssistantsTeamName; + + /** + * The prefix used to prepend the names of student repositories. + * Should ideally contain the current school year. + */ + private String studentRepoPrefix; + + /** + * The HttpClient object to use with all requests. + */ + private HttpClient client; + + /** + * ObjectMapper to use when constructing JSON objects. + */ + private ObjectMapper mapper; + /** * The logger for outputting debug info. */ @@ -60,11 +84,15 @@ public class Initializer { logger.setParent(Logger.getGlobal()); } - public Initializer(String organizationName, String accessToken, String assignmentsRepo) { + public Initializer(String organizationName, String accessToken, String assignmentsRepo, String teachingAssistantsTeamName, String studentRepoPrefix) { this.organizationName = organizationName; this.accessToken = accessToken; this.assignmentsRepo = assignmentsRepo; + this.teachingAssistantsTeamName = teachingAssistantsTeamName; + this.studentRepoPrefix = studentRepoPrefix; this.urlBuilder = new URLBuilder(organizationName, accessToken); + this.client = HttpClientBuilder.create().build(); + this.mapper = new ObjectMapper(); } /** @@ -79,18 +107,271 @@ public class Initializer { * - adds students to repository * - adds all students to assignments repository. * @param studentTeams The list of student studentTeams. + * @throws Exception If an error occurs while initializing the github repositories. */ - public void initializeGithubRepos(List studentTeams) { + public void initializeGithubRepos(List studentTeams) throws Exception { List taTeams = listMemberTeams(); - TATeam teamAll = this.getTeamAll(taTeams, "teaching-assistants"); - this.createRepository(this.assignmentsRepo, teamAll); + TATeam teamAll = this.getTeamAll(taTeams, this.teachingAssistantsTeamName); + + this.setupAssignmentsRepo(teamAll); + } - private boolean createRepository(String repositoryName, TATeam taTeam) { + /** + * Sets up the organization's assignments repository, and grants permissions to all teaching assistants. + * @param allTeachingAssistants A team consisting of all teaching assistants. + * @return True if everything went successfully, or false if an error occurred. + * @throws IOException If an HTTP request failed. + */ + private boolean setupAssignmentsRepo(TATeam allTeachingAssistants) throws IOException { + // First, create the assignments repository. + boolean successCreate = this.createRepository(this.assignmentsRepo, allTeachingAssistants); + if (successCreate) { + logger.info("Created repository: " + this.assignmentsRepo); + } else { + logger.severe("Could not create repository: " + this.assignmentsRepo); + } + + // Then, protect the master branch. + boolean successProtect = this.protectMasterBranch(this.assignmentsRepo, allTeachingAssistants); + if (successProtect) { + logger.info("Protected master branch of: " + this.assignmentsRepo); + } else { + logger.severe("Could not protect master branch of: " + this.assignmentsRepo); + } + + // And finally give all teaching assistants admin permissions to the repository. + boolean successGrant = this.grantTeamPermissions(this.assignmentsRepo, allTeachingAssistants); + if (successGrant) { + logger.info("Granted permissions to teaching assistants for: " + this.assignmentsRepo); + } else { + logger.severe("Could not grant permissions to teaching assistants for: " + this.assignmentsRepo); + } + + return successCreate && successProtect && successGrant; + } + + /** + * Creates and sets up a student team's repository, and invites those students to the organization's assignments + * repository as well. + * @param team The student team to set up. + * @param taTeam The team of teaching assistants that is responsible for these students. + * @return True if everything went according to plan, false if at least one error occurred. + * @throws IOException If an HTTP request fails. + */ + private boolean setupStudentTeam(StudentTeam team, TATeam taTeam) throws IOException { + String teamRepoName = team.generateUniqueName(this.studentRepoPrefix); + + // First create the repository. + boolean successCreate = this.createRepository(teamRepoName, taTeam); + if (successCreate) { + logger.info("Created repository: " + teamRepoName); + } else { + logger.severe("Could not create repository: " + teamRepoName); + } + + // Then protect the master branch. + boolean successProtect = this.protectMasterBranch(teamRepoName, taTeam); + if (successProtect) { + logger.info("Protected master branch of: " + teamRepoName); + } else { + logger.severe("Could not protect master branch of: " + teamRepoName); + } + + // And finally create the development branch. + boolean successDev = this.createDevelopmentBranch(teamRepoName); + if (successDev) { + logger.info("Created development branch for: " + teamRepoName); + } else { + logger.severe("Could not create development branch for: " + teamRepoName); + } + + boolean successGrant = this.grantTeamPermissions(teamRepoName, taTeam); + if (successGrant) { + logger.info("Granted permissions for: " + teamRepoName + " to " + taTeam.getName()); + } else { + logger.severe("Could not grant permissions for: " + teamRepoName + " to " + taTeam.getName()); + } + + Map studentSuccesses = new HashMap<>(); + // For every student in the team, add them to the organization's assignments repo, and their own. + for (Student student : team.getStudents()) { + // First invite to their own repository. + boolean successInviteOwn = this.addStudentToRepository(teamRepoName, student, true); + if (successInviteOwn) { + logger.info("Invited " + student + " to " + teamRepoName); + } else { + logger.severe("Could not invite " + student + " to " + teamRepoName); + } + + // Then invite the student to the assignments repo. + boolean successInviteAssign = this.addStudentToRepository(this.assignmentsRepo, student, false); + if (successInviteAssign) { + logger.info("Invited " + student + " to " + this.assignmentsRepo); + } else { + logger.severe("Could not invite " + student + " to " + this.assignmentsRepo); + } + + studentSuccesses.put(student, new Boolean[]{successInviteOwn, successInviteAssign}); + } + + // Calculate total success. + boolean allStudentSuccess = true; + for (Map.Entry entry : studentSuccesses.entrySet()) { + for (boolean b : entry.getValue()) { + allStudentSuccess &= b; + } + } + + return successCreate && successProtect && successDev && successGrant && allStudentSuccess; + } + + /** + * Invites a student as a collaborator to a repository. + * @param repositoryName The name of the repository to invite the student to. + * @param student The student to send the invitation to. + * @param writeAllowed Whether or not the student is allowed to push to the repository. + * @return True if successful and the invitation is created, false otherwise. + * @throws IOException If an error occurs while performing a request or with json compiling. + */ + private boolean addStudentToRepository(String repositoryName, Student student, boolean writeAllowed) throws IOException { + // First create a simple JSON object that either gives push or pull permission. + ObjectNode root = this.mapper.createObjectNode(); + root.put("permissions", (writeAllowed ? "push" : "pull")); + String json = this.mapper.writeValueAsString(root); + + // Then make the request. + HttpPut put = new HttpPut(this.urlBuilder.buildCollaboratorURL(repositoryName, student.getGithubUsername())); + put.setEntity(new StringEntity(json)); + logger.fine("Sending PUT request to add student: " + student + " to repository: " + repositoryName); + HttpResponse response = this.client.execute(put); + logger.finest("Response from PUT to add student to repository: " + response.getStatusLine().getStatusCode()); + + // Only if 201 is returned do we know that a collaborator is invited. + return response.getStatusLine().getStatusCode() == 201; + } + + /** + * Creates the development branch needed by all student repositories. + * @param repositoryName The name of the repository. + * @return True if the branch could successfully be created, false otherwise. + * @throws IOException If an error occurs while performing a request or with json compiling. + */ + private boolean createDevelopmentBranch(String repositoryName) throws IOException { + // First obtain a reference for the repository. + HttpGet get = new HttpGet(this.urlBuilder.buildReferenceGetURL(repositoryName)); + HttpResponse getResponse = this.client.execute(get); + if (getResponse.getStatusLine().getStatusCode() != 200) { + return false; + } + ObjectNode responseRoot = this.mapper.valueToTree(getResponseBody(getResponse)); + String sha = responseRoot.get("object").get("sha").textValue(); + + // Construct the request data. + ObjectNode root = this.mapper.createObjectNode(); + root.put("ref", "refs/heads/development"); + root.put("sha", sha); + String json = this.mapper.writeValueAsString(root); + + // Send the request. + HttpPost post = new HttpPost(this.urlBuilder.buildReferencePostURL(repositoryName)); + post.setEntity(new StringEntity(json)); + logger.fine("Sending POST request to create development branch for repository: " + repositoryName); + HttpResponse postResponse = this.client.execute(post); + logger.finest("Response from POST to create development branch: " + postResponse.getStatusLine().getStatusCode()); + + // Only if 201 is returned, is this successful. + return postResponse.getStatusLine().getStatusCode() == 201; + } + + /** + * Grants the given team admin permissions to the repository with the given name. + * @param repositoryName The name of the repository. + * @param team The team to give admin permissions to. + * @return True if rights were successfully granted, false otherwise. + * @throws IOException If an error occurs while attempting to give a team admin rights over the repository. + */ + private boolean grantTeamPermissions(String repositoryName, TATeam team) throws IOException { + // First, create the JSON object, which is simply one value pair. + ObjectNode root = this.mapper.createObjectNode(); + root.put("permission", "admin"); + String json = this.mapper.writeValueAsString(root); + + // Create and send request. + HttpPut put = new HttpPut(this.urlBuilder.buildTeamPermissionsURL(repositoryName, String.valueOf(team.getId()))); + put.setEntity(new StringEntity(json)); + logger.fine("Sending PUT request to add team " + team.getName() + " as admins for repo: " + repositoryName); + HttpResponse response = this.client.execute(put); + logger.finest("Response from adding team as admins: " + response.getStatusLine().getStatusCode()); + + // Return true only if request returns 204. + return response.getStatusLine().getStatusCode() == 204; + } + + /** + * Protects the master branch of the given repository. + * @see Github Branch Protection + * @param repositoryName The repository to protect the master branch of. + * @param taTeam The team to give rights to. + * @return True if the master branch has been protected, false otherwise. + * @throws IOException If an error occurs with requests or compiling json. + */ + private boolean protectMasterBranch(String repositoryName, TATeam taTeam) throws IOException { + // First, create quite a complex JSON object. + // settings + ObjectNode root = this.mapper.createObjectNode(); + // required_status_checks + { + ObjectNode requiredStatusChecksNode = this.mapper.createObjectNode(); + requiredStatusChecksNode.put("strict", false); + // contexts + { + ArrayNode contextsNode = this.mapper.createArrayNode(); + contextsNode.add("ci/circleci"); + requiredStatusChecksNode.set("contexts", contextsNode); + } + root.set("required_status_checks", requiredStatusChecksNode); + } + root.put("enforce_admins", false); + root.set("required_pull_request_reviews", this.mapper.createObjectNode()); + // restrictions + { + ObjectNode restrictionsNode = this.mapper.createObjectNode(); + restrictionsNode.set("users", this.mapper.createArrayNode()); + // teams + { + ArrayNode teamsNode = this.mapper.createArrayNode(); + teamsNode.add(taTeam.getName()); + restrictionsNode.set("teams", teamsNode); + } + root.set("restrictions", restrictionsNode); + } + String json = this.mapper.writer().writeValueAsString(root); + + // Create request and put the json to the server. + HttpPut put = new HttpPut(this.urlBuilder.buildBranchProtectionURL(repositoryName, "master")); + put.setEntity(new StringEntity(json)); + put.setHeader("Accept", "application/vnd.github.luke-cage-preview+json"); + logger.fine("Sending PUT request to protect master branch of repository: " + repositoryName); + HttpResponse response = this.client.execute(put); + logger.finest("Response from protecting master branch: " + response.getStatusLine().getStatusCode()); + logger.finest(getResponseBody(response)); + + // Return true only if the status code is 200. + return response.getStatusLine().getStatusCode() == 200; + } + + /** + * Creates a repository of the given name and assigns the given team to it. + * @param repositoryName The name of the repository to make. + * @param taTeam The team to assign to this repository. + * @return True if the repository was created successfully, or false if an error occurred. + * @throws IOException If an error occurs during http requesting or JSON processing. + */ + private boolean createRepository(String repositoryName, TATeam taTeam) throws IOException { logger.info("Creating repository: " + repositoryName); - ObjectMapper mapper = new ObjectMapper(); - ObjectNode root = mapper.createObjectNode(); + ObjectNode root = this.mapper.createObjectNode(); root.put("name", repositoryName); root.put("private", false); root.put("has_issues", true); @@ -98,25 +379,19 @@ public class Initializer { root.put("team_id", taTeam.getId()); root.put("auto_init", false); root.put("gitignore_template", "Java"); - String json = null; - try { - json = mapper.writer().writeValueAsString(root); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } + String json = this.mapper.writer().writeValueAsString(root); - HttpClient client = HttpClientBuilder.create().build(); + // Create the request and post it. HttpPost post = new HttpPost(this.urlBuilder.buildRepoURL()); - try { - post.setEntity(new StringEntity(json)); - post.setHeader("Content-type", "application/json"); - HttpResponse response = client.execute(post); - logger.info("Response from creating repository POST: " + response.getStatusLine().getStatusCode()); - logger.info(getResponseBody(response)); - } catch (IOException e) { - e.printStackTrace(); - } - return false; + post.setEntity(new StringEntity(json)); + post.setHeader("Content-type", "application/json"); + logger.fine("Sending POST request to create repository: " + repositoryName); + HttpResponse response = this.client.execute(post); + logger.finest("Response from creating repository POST: " + response.getStatusLine().getStatusCode()); + logger.finest(getResponseBody(response)); + + // Return true if 201 is the response code, and false otherwise. + return response.getStatusLine().getStatusCode() == 201; } /** @@ -124,14 +399,15 @@ public class Initializer { * @param taTeams The list of all teams in the organization. * @param teamAllName The name of the team representing all TA's. * @return The team which holds all TA's. + * @throws Exception If no team with the given name exists. */ - private TATeam getTeamAll(List taTeams, String teamAllName) { + private TATeam getTeamAll(List taTeams, String teamAllName) throws Exception { for (TATeam team : taTeams) { if (team.getName().equals(teamAllName)) { return team; } } - return null; + throw new Exception("No team with name " + teamAllName + " could be found."); } /** diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 16316d7..44cc264 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -79,16 +79,16 @@ public class TeamGenerator { int teamCount = 1; // For each student, try to make a team from its preferred partners. for (Map.Entry e : studentMap.entrySet()) { - StudentTeam t = e.getValue().getPreferredTeam(studentMap); - logger.finest("Checking if student's preferred team is valid: " + t.getStudents()); + StudentTeam newTeam = e.getValue().getPreferredTeam(studentMap); + logger.finest("Checking if student's preferred team is valid: " + newTeam.getStudents()); // Check if the team is of a valid size, and is not a duplicate. // Note that at this stage, singles are treated as studentTeams of 1, and thus not valid for any teamSize > 1. - if (t.isValid(teamSize) && !studentTeams.contains(t)) { + if (newTeam.isValid(teamSize) && !studentTeams.contains(newTeam)) { // Once we know this team is completely valid, we remove all the students in it from the list of singles. - t.setId(teamCount++); - singleStudents.removeAll(t.getStudents()); - studentTeams.add(t); - logger.fine("Created team: " + t); + newTeam.setId(teamCount++); + singleStudents.removeAll(newTeam.getStudents()); + studentTeams.add(newTeam); + logger.fine("Created team: " + newTeam); } } From 46757539a181627be76182f74d6415e784663318 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 16 Aug 2018 14:40:45 +0200 Subject: [PATCH 06/19] Removed ugly http requests and use a github api now. Everything is a lot cleaner, and I can initialize student repositories now. --- pom.xml | 6 + src/main/java/nl/andrewlalis/Main.java | 19 +- .../nl/andrewlalis/git_api/GithubManager.java | 202 ++++++++ .../nl/andrewlalis/git_api/Initializer.java | 451 ------------------ .../java/nl/andrewlalis/model/TATeam.java | 6 +- .../andrewlalis/model/TeachingAssistant.java | 2 + .../java/nl/andrewlalis/util/CommandLine.java | 2 +- 7 files changed, 225 insertions(+), 463 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/git_api/GithubManager.java delete mode 100644 src/main/java/nl/andrewlalis/git_api/Initializer.java diff --git a/pom.xml b/pom.xml index 4114823..2510ab2 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,12 @@ jackson-annotations ${jackson.version} + + + org.kohsuke + github-api + 1.93 + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 6fb18d1..ad58dab 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,6 +1,6 @@ package nl.andrewlalis; -import nl.andrewlalis.git_api.Initializer; +import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.util.Logging; import nl.andrewlalis.util.TeamGenerator; @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; @@ -28,20 +29,25 @@ public class Main { // Initialize logger. ConsoleHandler handler = new ConsoleHandler(); - handler.setLevel(Level.FINEST); - logger.addHandler(handler); + handler.setLevel(Level.INFO); try { Logging.setup(true); // TODO: Replace true with command line arg. + Handler[] handlers = logger.getHandlers(); + for (Handler h : handlers) { + logger.removeHandler(h); + } + logger.setUseParentHandlers(false); + logger.addHandler(handler); } catch (IOException e) { logger.severe("Unable to save log to file."); } - logger.info("Initializer for Github Repositories in Educational Organizations."); + logger.info("GithubManager for Github Repositories in Educational Organizations."); // Get studentTeams from CSV file. List studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize"))); - Initializer initializer = new Initializer( + GithubManager githubManager = new GithubManager( userOptions.get("organization"), userOptions.get("token"), "assignments_2018", @@ -50,7 +56,8 @@ public class Main { ); try { - initializer.initializeGithubRepos(studentTeams); + githubManager.initializeGithubRepos(studentTeams); + //githubManager.deleteAllRepositories(); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java new file mode 100644 index 0000000..011d51d --- /dev/null +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -0,0 +1,202 @@ +package nl.andrewlalis.git_api; + +import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.StudentTeam; +import org.kohsuke.github.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +/** + * This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc. + */ +public class GithubManager { + + /** + * The name of the organization to operate on. + */ + private String organizationName; + + /** + * The object which simplifies creating REST URL's for most of the requests. + */ + private URLBuilder urlBuilder; + + /** + * The assignments repository where students will get assignments from. + */ + private GHRepository assignmentsRepo; + private String assignmentsRepoName; + + /** + * The name of the team which contains all teaching assistants. + */ + private String teachingAssistantsTeamName; + + /** + * The prefix used to prepend the names of student repositories. + * Should ideally contain the current school year. + */ + private String studentRepoPrefix; + + /** + * Github object for API interactions. + */ + private GitHub github; + private GHOrganization organization; + + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(GithubManager.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + + public GithubManager(String organizationName, String accessToken, String assignmentsRepo, String teachingAssistantsTeamName, String studentRepoPrefix) { + this.organizationName = organizationName; + this.assignmentsRepoName = assignmentsRepo; + this.teachingAssistantsTeamName = teachingAssistantsTeamName; + this.studentRepoPrefix = studentRepoPrefix; + this.urlBuilder = new URLBuilder(organizationName, accessToken); + + try { + this.github = GitHub.connectUsingOAuth(accessToken); + this.organization = this.github.getOrganization(this.organizationName); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Initializes the github repository for all studentTeams given. + * + * Creates for the entire organization: + * - an assignments repository with protected master branch and TA permissions. + * Creates for each team: + * - a repository + * - protected master branch + * - development branch + * - adds students to repository + * - adds all students to assignments repository. + * @param studentTeams The list of student studentTeams. + * @throws Exception If an error occurs while initializing the github repositories. + */ + public void initializeGithubRepos(List studentTeams) throws Exception { + GHTeam teamAll = this.organization.getTeamByName(this.teachingAssistantsTeamName); + + this.setupAssignmentsRepo(teamAll); + + StudentTeam t = new StudentTeam(); + Student s = new Student(3050831, "Andrew Lalis", "andrewlalisofficial@gmail.com", "andrewlalis", null); + t.addStudent(s); + t.setId(42); + + this.setupStudentTeam(t, teamAll); + } + + /** + * Sets up the organization's assignments repository, and grants permissions to all teaching assistants. + * @param allTeachingAssistants A team consisting of all teaching assistants. + * @throws IOException If an HTTP request failed. + */ + private void setupAssignmentsRepo(GHTeam allTeachingAssistants) throws IOException { + // Create the repository. + GHCreateRepositoryBuilder builder = this.organization.createRepository(this.assignmentsRepoName); + builder.description("Assignments repository for Advanced Object Oriented Programming"); + builder.wiki(false); + builder.issues(true); + builder.private_(false); // TODO: Make this true for production. + builder.team(allTeachingAssistants); + builder.gitignoreTemplate("Java"); + this.assignmentsRepo = builder.create(); + + // Protect the master branch. + GHBranchProtectionBuilder protectionBuilder = this.assignmentsRepo.getBranch("master").enableProtection(); + protectionBuilder.includeAdmins(false); + protectionBuilder.restrictPushAccess(); + protectionBuilder.teamPushAccess(allTeachingAssistants); + protectionBuilder.addRequiredChecks("ci/circleci"); + protectionBuilder.enable(); + + // Grant all teaching assistants write access. + allTeachingAssistants.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); + } + + /** + * Creates and sets up a student team's repository, and invites those students to the organization's assignments + * repository as well. + * @param team The student team to set up. + * @param taTeam The team of teaching assistants that is responsible for these students. + * @throws IOException If an HTTP request fails. + */ + private void setupStudentTeam(StudentTeam team, GHTeam taTeam) throws IOException { + String teamRepoName = team.generateUniqueName(this.studentRepoPrefix); + + List students = team.getStudents(); + StringBuilder description = new StringBuilder("Group "); + description.append(team.getId()).append(": "); + + for (Student s : students) { + description.append(s.getName()).append(' '); + } + + GHCreateRepositoryBuilder builder = this.organization.createRepository(teamRepoName); + builder.team(taTeam); + builder.wiki(false); + builder.issues(true); + builder.description(description.toString()); + builder.gitignoreTemplate("Java"); + builder.private_(false); // TODO: Change this to true for production + GHRepository repo = builder.create(); + logger.info("Created repository: " + repo.getName()); + + // Protect the master branch. + GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection(); + protectionBuilder.includeAdmins(false); + protectionBuilder.teamPushAccess(taTeam); + protectionBuilder.addRequiredChecks("ci/circleci"); + protectionBuilder.enable(); + logger.fine("Protected master branch of repository: " + repo.getName()); + + // Create development branch. + String sha1 = repo.getBranch(repo.getDefaultBranch()).getSHA1(); + repo.createRef("refs/heads/development", sha1); + logger.fine("Created development branch of repository: " + repo.getName()); + + taTeam.add(repo, GHOrganization.Permission.ADMIN); + logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + repo.getName()); + + List users = new ArrayList<>(); + for (Student student : team.getStudents()) { + GHUser user = this.github.getUser(student.getGithubUsername()); + users.add(user); + } + + repo.addCollaborators(users); + this.assignmentsRepo.addCollaborators(users); + } + + /** + * Deletes all repositories in the organization. + * @throws IOException if an error occurs with sending requests. + */ + public void deleteAllRepositories() throws IOException { + Map repoMap = this.organization.getRepositories(); + for (Map.Entry repoEntry : repoMap.entrySet()) { + repoEntry.getValue().delete(); + } + } + + /** + * Archives all repositories whose name contains the given substring. + * @param sub Any repository containing this substring will be archived. + */ + public void archiveAllRepositories(String sub) { + + } + +} diff --git a/src/main/java/nl/andrewlalis/git_api/Initializer.java b/src/main/java/nl/andrewlalis/git_api/Initializer.java deleted file mode 100644 index 086c6a6..0000000 --- a/src/main/java/nl/andrewlalis/git_api/Initializer.java +++ /dev/null @@ -1,451 +0,0 @@ -package nl.andrewlalis.git_api; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import nl.andrewlalis.model.Student; -import nl.andrewlalis.model.StudentTeam; -import nl.andrewlalis.model.TATeam; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -/** - * This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc. - */ -public class Initializer { - - /** - * The name of the organization to operate on. - */ - private String organizationName; - - /** - * The oauth access token needed for the request. - */ - private String accessToken; - - /** - * The object which simplifies creating REST URL's for most of the requests. - */ - private URLBuilder urlBuilder; - - /** - * The name of the assignments repository where students will get assignments from. - */ - private String assignmentsRepo; - - /** - * The name of the team which contains all teaching assistants. - */ - private String teachingAssistantsTeamName; - - /** - * The prefix used to prepend the names of student repositories. - * Should ideally contain the current school year. - */ - private String studentRepoPrefix; - - /** - * The HttpClient object to use with all requests. - */ - private HttpClient client; - - /** - * ObjectMapper to use when constructing JSON objects. - */ - private ObjectMapper mapper; - - /** - * The logger for outputting debug info. - */ - private static final Logger logger = Logger.getLogger(Initializer.class.getName()); - static { - logger.setParent(Logger.getGlobal()); - } - - public Initializer(String organizationName, String accessToken, String assignmentsRepo, String teachingAssistantsTeamName, String studentRepoPrefix) { - this.organizationName = organizationName; - this.accessToken = accessToken; - this.assignmentsRepo = assignmentsRepo; - this.teachingAssistantsTeamName = teachingAssistantsTeamName; - this.studentRepoPrefix = studentRepoPrefix; - this.urlBuilder = new URLBuilder(organizationName, accessToken); - this.client = HttpClientBuilder.create().build(); - this.mapper = new ObjectMapper(); - } - - /** - * Initializes the github repository for all studentTeams given. - * - * Creates for the entire organization: - * - an assignments repository with protected master branch and TA permissions. - * Creates for each team: - * - a repository - * - protected master branch - * - development branch - * - adds students to repository - * - adds all students to assignments repository. - * @param studentTeams The list of student studentTeams. - * @throws Exception If an error occurs while initializing the github repositories. - */ - public void initializeGithubRepos(List studentTeams) throws Exception { - List taTeams = listMemberTeams(); - TATeam teamAll = this.getTeamAll(taTeams, this.teachingAssistantsTeamName); - - this.setupAssignmentsRepo(teamAll); - - } - - /** - * Sets up the organization's assignments repository, and grants permissions to all teaching assistants. - * @param allTeachingAssistants A team consisting of all teaching assistants. - * @return True if everything went successfully, or false if an error occurred. - * @throws IOException If an HTTP request failed. - */ - private boolean setupAssignmentsRepo(TATeam allTeachingAssistants) throws IOException { - // First, create the assignments repository. - boolean successCreate = this.createRepository(this.assignmentsRepo, allTeachingAssistants); - if (successCreate) { - logger.info("Created repository: " + this.assignmentsRepo); - } else { - logger.severe("Could not create repository: " + this.assignmentsRepo); - } - - // Then, protect the master branch. - boolean successProtect = this.protectMasterBranch(this.assignmentsRepo, allTeachingAssistants); - if (successProtect) { - logger.info("Protected master branch of: " + this.assignmentsRepo); - } else { - logger.severe("Could not protect master branch of: " + this.assignmentsRepo); - } - - // And finally give all teaching assistants admin permissions to the repository. - boolean successGrant = this.grantTeamPermissions(this.assignmentsRepo, allTeachingAssistants); - if (successGrant) { - logger.info("Granted permissions to teaching assistants for: " + this.assignmentsRepo); - } else { - logger.severe("Could not grant permissions to teaching assistants for: " + this.assignmentsRepo); - } - - return successCreate && successProtect && successGrant; - } - - /** - * Creates and sets up a student team's repository, and invites those students to the organization's assignments - * repository as well. - * @param team The student team to set up. - * @param taTeam The team of teaching assistants that is responsible for these students. - * @return True if everything went according to plan, false if at least one error occurred. - * @throws IOException If an HTTP request fails. - */ - private boolean setupStudentTeam(StudentTeam team, TATeam taTeam) throws IOException { - String teamRepoName = team.generateUniqueName(this.studentRepoPrefix); - - // First create the repository. - boolean successCreate = this.createRepository(teamRepoName, taTeam); - if (successCreate) { - logger.info("Created repository: " + teamRepoName); - } else { - logger.severe("Could not create repository: " + teamRepoName); - } - - // Then protect the master branch. - boolean successProtect = this.protectMasterBranch(teamRepoName, taTeam); - if (successProtect) { - logger.info("Protected master branch of: " + teamRepoName); - } else { - logger.severe("Could not protect master branch of: " + teamRepoName); - } - - // And finally create the development branch. - boolean successDev = this.createDevelopmentBranch(teamRepoName); - if (successDev) { - logger.info("Created development branch for: " + teamRepoName); - } else { - logger.severe("Could not create development branch for: " + teamRepoName); - } - - boolean successGrant = this.grantTeamPermissions(teamRepoName, taTeam); - if (successGrant) { - logger.info("Granted permissions for: " + teamRepoName + " to " + taTeam.getName()); - } else { - logger.severe("Could not grant permissions for: " + teamRepoName + " to " + taTeam.getName()); - } - - Map studentSuccesses = new HashMap<>(); - // For every student in the team, add them to the organization's assignments repo, and their own. - for (Student student : team.getStudents()) { - // First invite to their own repository. - boolean successInviteOwn = this.addStudentToRepository(teamRepoName, student, true); - if (successInviteOwn) { - logger.info("Invited " + student + " to " + teamRepoName); - } else { - logger.severe("Could not invite " + student + " to " + teamRepoName); - } - - // Then invite the student to the assignments repo. - boolean successInviteAssign = this.addStudentToRepository(this.assignmentsRepo, student, false); - if (successInviteAssign) { - logger.info("Invited " + student + " to " + this.assignmentsRepo); - } else { - logger.severe("Could not invite " + student + " to " + this.assignmentsRepo); - } - - studentSuccesses.put(student, new Boolean[]{successInviteOwn, successInviteAssign}); - } - - // Calculate total success. - boolean allStudentSuccess = true; - for (Map.Entry entry : studentSuccesses.entrySet()) { - for (boolean b : entry.getValue()) { - allStudentSuccess &= b; - } - } - - return successCreate && successProtect && successDev && successGrant && allStudentSuccess; - } - - /** - * Invites a student as a collaborator to a repository. - * @param repositoryName The name of the repository to invite the student to. - * @param student The student to send the invitation to. - * @param writeAllowed Whether or not the student is allowed to push to the repository. - * @return True if successful and the invitation is created, false otherwise. - * @throws IOException If an error occurs while performing a request or with json compiling. - */ - private boolean addStudentToRepository(String repositoryName, Student student, boolean writeAllowed) throws IOException { - // First create a simple JSON object that either gives push or pull permission. - ObjectNode root = this.mapper.createObjectNode(); - root.put("permissions", (writeAllowed ? "push" : "pull")); - String json = this.mapper.writeValueAsString(root); - - // Then make the request. - HttpPut put = new HttpPut(this.urlBuilder.buildCollaboratorURL(repositoryName, student.getGithubUsername())); - put.setEntity(new StringEntity(json)); - logger.fine("Sending PUT request to add student: " + student + " to repository: " + repositoryName); - HttpResponse response = this.client.execute(put); - logger.finest("Response from PUT to add student to repository: " + response.getStatusLine().getStatusCode()); - - // Only if 201 is returned do we know that a collaborator is invited. - return response.getStatusLine().getStatusCode() == 201; - } - - /** - * Creates the development branch needed by all student repositories. - * @param repositoryName The name of the repository. - * @return True if the branch could successfully be created, false otherwise. - * @throws IOException If an error occurs while performing a request or with json compiling. - */ - private boolean createDevelopmentBranch(String repositoryName) throws IOException { - // First obtain a reference for the repository. - HttpGet get = new HttpGet(this.urlBuilder.buildReferenceGetURL(repositoryName)); - HttpResponse getResponse = this.client.execute(get); - if (getResponse.getStatusLine().getStatusCode() != 200) { - return false; - } - ObjectNode responseRoot = this.mapper.valueToTree(getResponseBody(getResponse)); - String sha = responseRoot.get("object").get("sha").textValue(); - - // Construct the request data. - ObjectNode root = this.mapper.createObjectNode(); - root.put("ref", "refs/heads/development"); - root.put("sha", sha); - String json = this.mapper.writeValueAsString(root); - - // Send the request. - HttpPost post = new HttpPost(this.urlBuilder.buildReferencePostURL(repositoryName)); - post.setEntity(new StringEntity(json)); - logger.fine("Sending POST request to create development branch for repository: " + repositoryName); - HttpResponse postResponse = this.client.execute(post); - logger.finest("Response from POST to create development branch: " + postResponse.getStatusLine().getStatusCode()); - - // Only if 201 is returned, is this successful. - return postResponse.getStatusLine().getStatusCode() == 201; - } - - /** - * Grants the given team admin permissions to the repository with the given name. - * @param repositoryName The name of the repository. - * @param team The team to give admin permissions to. - * @return True if rights were successfully granted, false otherwise. - * @throws IOException If an error occurs while attempting to give a team admin rights over the repository. - */ - private boolean grantTeamPermissions(String repositoryName, TATeam team) throws IOException { - // First, create the JSON object, which is simply one value pair. - ObjectNode root = this.mapper.createObjectNode(); - root.put("permission", "admin"); - String json = this.mapper.writeValueAsString(root); - - // Create and send request. - HttpPut put = new HttpPut(this.urlBuilder.buildTeamPermissionsURL(repositoryName, String.valueOf(team.getId()))); - put.setEntity(new StringEntity(json)); - logger.fine("Sending PUT request to add team " + team.getName() + " as admins for repo: " + repositoryName); - HttpResponse response = this.client.execute(put); - logger.finest("Response from adding team as admins: " + response.getStatusLine().getStatusCode()); - - // Return true only if request returns 204. - return response.getStatusLine().getStatusCode() == 204; - } - - /** - * Protects the master branch of the given repository. - * @see Github Branch Protection - * @param repositoryName The repository to protect the master branch of. - * @param taTeam The team to give rights to. - * @return True if the master branch has been protected, false otherwise. - * @throws IOException If an error occurs with requests or compiling json. - */ - private boolean protectMasterBranch(String repositoryName, TATeam taTeam) throws IOException { - // First, create quite a complex JSON object. - // settings - ObjectNode root = this.mapper.createObjectNode(); - // required_status_checks - { - ObjectNode requiredStatusChecksNode = this.mapper.createObjectNode(); - requiredStatusChecksNode.put("strict", false); - // contexts - { - ArrayNode contextsNode = this.mapper.createArrayNode(); - contextsNode.add("ci/circleci"); - requiredStatusChecksNode.set("contexts", contextsNode); - } - root.set("required_status_checks", requiredStatusChecksNode); - } - root.put("enforce_admins", false); - root.set("required_pull_request_reviews", this.mapper.createObjectNode()); - // restrictions - { - ObjectNode restrictionsNode = this.mapper.createObjectNode(); - restrictionsNode.set("users", this.mapper.createArrayNode()); - // teams - { - ArrayNode teamsNode = this.mapper.createArrayNode(); - teamsNode.add(taTeam.getName()); - restrictionsNode.set("teams", teamsNode); - } - root.set("restrictions", restrictionsNode); - } - String json = this.mapper.writer().writeValueAsString(root); - - // Create request and put the json to the server. - HttpPut put = new HttpPut(this.urlBuilder.buildBranchProtectionURL(repositoryName, "master")); - put.setEntity(new StringEntity(json)); - put.setHeader("Accept", "application/vnd.github.luke-cage-preview+json"); - logger.fine("Sending PUT request to protect master branch of repository: " + repositoryName); - HttpResponse response = this.client.execute(put); - logger.finest("Response from protecting master branch: " + response.getStatusLine().getStatusCode()); - logger.finest(getResponseBody(response)); - - // Return true only if the status code is 200. - return response.getStatusLine().getStatusCode() == 200; - } - - /** - * Creates a repository of the given name and assigns the given team to it. - * @param repositoryName The name of the repository to make. - * @param taTeam The team to assign to this repository. - * @return True if the repository was created successfully, or false if an error occurred. - * @throws IOException If an error occurs during http requesting or JSON processing. - */ - private boolean createRepository(String repositoryName, TATeam taTeam) throws IOException { - logger.info("Creating repository: " + repositoryName); - - ObjectNode root = this.mapper.createObjectNode(); - root.put("name", repositoryName); - root.put("private", false); - root.put("has_issues", true); - root.put("has_wiki", true); - root.put("team_id", taTeam.getId()); - root.put("auto_init", false); - root.put("gitignore_template", "Java"); - String json = this.mapper.writer().writeValueAsString(root); - - // Create the request and post it. - HttpPost post = new HttpPost(this.urlBuilder.buildRepoURL()); - post.setEntity(new StringEntity(json)); - post.setHeader("Content-type", "application/json"); - logger.fine("Sending POST request to create repository: " + repositoryName); - HttpResponse response = this.client.execute(post); - logger.finest("Response from creating repository POST: " + response.getStatusLine().getStatusCode()); - logger.finest(getResponseBody(response)); - - // Return true if 201 is the response code, and false otherwise. - return response.getStatusLine().getStatusCode() == 201; - } - - /** - * Extracts the team containing all TA's from a list of all teams. - * @param taTeams The list of all teams in the organization. - * @param teamAllName The name of the team representing all TA's. - * @return The team which holds all TA's. - * @throws Exception If no team with the given name exists. - */ - private TATeam getTeamAll(List taTeams, String teamAllName) throws Exception { - for (TATeam team : taTeams) { - if (team.getName().equals(teamAllName)) { - return team; - } - } - throw new Exception("No team with name " + teamAllName + " could be found."); - } - - /** - * Obtains a list of all teams in the organization, meaning all teaching assistant teams. - * @return A list of all teams in the organization. - */ - private List listMemberTeams() { - CloseableHttpClient client = HttpClients.createDefault(); - HttpGet get = new HttpGet(this.urlBuilder.buildTeamURL()); - try (CloseableHttpResponse response = client.execute(get)) { - StatusLine line = response.getStatusLine(); - logger.finer("Response code from listing member teams: " + line.getStatusCode()); - String result = getResponseBody(response); - logger.finer("Response entity: " + result); - - ObjectMapper mapper = new ObjectMapper(); - List memberTeams = mapper.readValue(result, new TypeReference>(){}); - for (TATeam r : memberTeams) { - logger.finest(r.toString()); - } - return memberTeams; - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - /** - * Extracts a string which represents the response body of an HttpResponse. - * @param response The response to a previous request. - * @return A string containing the whole response body. - */ - private static String getResponseBody(HttpResponse response) { - try { - return new BufferedReader(new InputStreamReader(response.getEntity().getContent())).lines().collect(Collectors.joining("\n")); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } -} diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 572b77a..0bbcdf3 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -11,7 +11,6 @@ import java.util.List; * Represents a teaching assistant team, which is itself a 'team' in the organization. This class is used for parsing * json from requests to github to get a list of all teams in the organization. */ -@JsonIgnoreProperties(ignoreUnknown = true) public class TATeam { private List teachingAssistants; @@ -19,13 +18,11 @@ public class TATeam { /** * The team's display name. */ - @JsonProperty("name") private String name; /** * The team's unique identifier. */ - @JsonProperty("id") private int id; /** @@ -33,8 +30,7 @@ public class TATeam { * @param name The name of the team. * @param id The unique identifier for this team. */ - @JsonCreator - public TATeam(@JsonProperty("name") String name, @JsonProperty("id") int id) { + public TATeam(String name, int id) { this.name = name; this.id = id; this.teachingAssistants = new ArrayList(); diff --git a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java index 918bb5c..f04723f 100644 --- a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java +++ b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java @@ -1,5 +1,7 @@ package nl.andrewlalis.model; +import org.kohsuke.github.GHTeam; + public class TeachingAssistant extends Person { /** diff --git a/src/main/java/nl/andrewlalis/util/CommandLine.java b/src/main/java/nl/andrewlalis/util/CommandLine.java index 8e4e206..1075d9e 100644 --- a/src/main/java/nl/andrewlalis/util/CommandLine.java +++ b/src/main/java/nl/andrewlalis/util/CommandLine.java @@ -67,7 +67,7 @@ public class CommandLine { options.addOption(organizationInput); // The maximum team size. - Option teamSizeInput = new Option("s", "teamsize", true, "The maximum size of teams to generate."); + Option teamSizeInput = new Option("ts", "teamsize", true, "The maximum size of teams to generate."); teamSizeInput.setRequired(false); options.addOption(teamSizeInput); From e901b0affd396f0270bc179da55e66bce2cdb760 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Fri, 17 Aug 2018 07:51:34 +0200 Subject: [PATCH 07/19] Added some package-info files, added a method to archive repositories which contain a substring, and did some more testing. Will add a user interface soon. --- pom.xml | 6 + src/main/java/nl/andrewlalis/Main.java | 4 +- .../nl/andrewlalis/git_api/GithubManager.java | 60 +++++--- .../nl/andrewlalis/git_api/URLBuilder.java | 140 ------------------ .../nl/andrewlalis/git_api/package-info.java | 7 + .../andrewlalis/model/TeachingAssistant.java | 2 - .../nl/andrewlalis/model/package-info.java | 7 + 7 files changed, 64 insertions(+), 162 deletions(-) delete mode 100644 src/main/java/nl/andrewlalis/git_api/URLBuilder.java create mode 100644 src/main/java/nl/andrewlalis/git_api/package-info.java create mode 100644 src/main/java/nl/andrewlalis/model/package-info.java diff --git a/pom.xml b/pom.xml index 2510ab2..7137563 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,12 @@ github-api 1.93 + + + org.xerial + sqlite-jdbc + 3.23.1 + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index ad58dab..ce4ea6e 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -56,8 +56,8 @@ public class Main { ); try { - githubManager.initializeGithubRepos(studentTeams); - //githubManager.deleteAllRepositories(); + //githubManager.initializeGithubRepos(studentTeams); + //githubManager.archiveAllRepositories("team"); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 011d51d..882658c 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -1,7 +1,14 @@ package nl.andrewlalis.git_api; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.kohsuke.github.*; import java.io.IOException; @@ -15,16 +22,6 @@ import java.util.logging.Logger; */ public class GithubManager { - /** - * The name of the organization to operate on. - */ - private String organizationName; - - /** - * The object which simplifies creating REST URL's for most of the requests. - */ - private URLBuilder urlBuilder; - /** * The assignments repository where students will get assignments from. */ @@ -47,6 +44,7 @@ public class GithubManager { */ private GitHub github; private GHOrganization organization; + private String accessToken; /** * The logger for outputting debug info. @@ -57,15 +55,14 @@ public class GithubManager { } public GithubManager(String organizationName, String accessToken, String assignmentsRepo, String teachingAssistantsTeamName, String studentRepoPrefix) { - this.organizationName = organizationName; this.assignmentsRepoName = assignmentsRepo; this.teachingAssistantsTeamName = teachingAssistantsTeamName; this.studentRepoPrefix = studentRepoPrefix; - this.urlBuilder = new URLBuilder(organizationName, accessToken); + this.accessToken = accessToken; try { this.github = GitHub.connectUsingOAuth(accessToken); - this.organization = this.github.getOrganization(this.organizationName); + this.organization = this.github.getOrganization(organizationName); } catch (IOException e) { e.printStackTrace(); } @@ -104,6 +101,13 @@ public class GithubManager { * @throws IOException If an HTTP request failed. */ private void setupAssignmentsRepo(GHTeam allTeachingAssistants) throws IOException { + // Check if the repository already exists. + GHRepository existingRepo = this.organization.getRepository(this.assignmentsRepoName); + if (existingRepo != null) { + existingRepo.delete(); + logger.fine("Deleted pre-existing assignments repository."); + } + // Create the repository. GHCreateRepositoryBuilder builder = this.organization.createRepository(this.assignmentsRepoName); builder.description("Assignments repository for Advanced Object Oriented Programming"); @@ -113,6 +117,7 @@ public class GithubManager { builder.team(allTeachingAssistants); builder.gitignoreTemplate("Java"); this.assignmentsRepo = builder.create(); + logger.info("Created assignments repository."); // Protect the master branch. GHBranchProtectionBuilder protectionBuilder = this.assignmentsRepo.getBranch("master").enableProtection(); @@ -121,9 +126,11 @@ public class GithubManager { protectionBuilder.teamPushAccess(allTeachingAssistants); protectionBuilder.addRequiredChecks("ci/circleci"); protectionBuilder.enable(); + logger.fine("Protected master branch of assignments repository."); // Grant all teaching assistants write access. allTeachingAssistants.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); + logger.fine("Gave admin rights to all teaching assistants in team: " + allTeachingAssistants.getName()); } /** @@ -185,9 +192,9 @@ public class GithubManager { * @throws IOException if an error occurs with sending requests. */ public void deleteAllRepositories() throws IOException { - Map repoMap = this.organization.getRepositories(); - for (Map.Entry repoEntry : repoMap.entrySet()) { - repoEntry.getValue().delete(); + List repositories = this.organization.listRepositories().asList(); + for (GHRepository repo : repositories) { + repo.delete(); } } @@ -195,8 +202,25 @@ public class GithubManager { * Archives all repositories whose name contains the given substring. * @param sub Any repository containing this substring will be archived. */ - public void archiveAllRepositories(String sub) { - + public void archiveAllRepositories(String sub) throws IOException { + List repositories = this.organization.listRepositories().asList(); + for (GHRepository repo : repositories) { + if (repo.getName().contains(sub)) { + HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken); + CloseableHttpClient client = HttpClientBuilder.create().build(); + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = mapper.createObjectNode(); + root.put("archived", true); + String json = mapper.writeValueAsString(root); + patch.setEntity(new StringEntity(json)); + HttpResponse response = client.execute(patch); + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode()); + } + logger.info("Archived repository: " + repo.getFullName()); + // TODO: archive repository using Github Java API, instead of Apache HttpUtils. + } + } } } diff --git a/src/main/java/nl/andrewlalis/git_api/URLBuilder.java b/src/main/java/nl/andrewlalis/git_api/URLBuilder.java deleted file mode 100644 index 5966224..0000000 --- a/src/main/java/nl/andrewlalis/git_api/URLBuilder.java +++ /dev/null @@ -1,140 +0,0 @@ -package nl.andrewlalis.git_api; - -public class URLBuilder { - - /** - * The main URL from which all requests must be formed. - */ - private static final String baseURL = "https://api.github.com"; - - /** - * The name of the github organization in which to create repositories. - */ - private String organizationName; - - /** - * The token needed to use the API. - */ - private String accessToken; - - public URLBuilder(String organizationName, String accessToken) { - this.organizationName = organizationName; - this.accessToken = accessToken; - } - - /** - * @return The URL for adding a repository. - */ - public String buildRepoURL() { - return baseURL - + "/orgs/" - + this.organizationName - + "/repos?access_token=" - + this.accessToken; - } - - /** - * @param repoName The name of the repository. - * @param branch The name of the branch in the repository above. - * @return The URL for setting branch protection. - */ - public String buildBranchProtectionURL(String repoName, String branch) { - return baseURL - + "/repos/" - + this.organizationName - + '/' - + repoName - + "/branches/" - + branch - + "/protection?access_token=" - + this.accessToken; - } - - /** - * @param repoName The name of the repository the branch is in. - * @return The URL for getting a branch reference. - */ - public String buildReferenceGetURL(String repoName) { - return baseURL - + "/repos/" - + this.organizationName - + '/' - + repoName - + "/git/refs/heads/master?access_token=" - + this.accessToken; - } - - /** - * @param repoName The repository name. - * @return The URL for creating a new branch, once a reference has been obtained. - */ - public String buildReferencePostURL(String repoName) { - return baseURL - + "/repos/" - + this.organizationName - + '/' - + repoName - + "/git/refs?access_token=" - + this.accessToken; - } - - /** - * @param repoName The name of the repository. - * @param collaborator The collaborator's name. - * @return The URL for adding a collaborator to a repository. - */ - public String buildCollaboratorURL(String repoName, String collaborator) { - return baseURL - + "/repos/" - + this.organizationName - + '/' - + repoName - + "/collaborators/" - + collaborator - + "?access_token=" - + this.accessToken; - } - - /** - * @return The URL for obtaining the teams. - */ - public String buildTeamURL() { - return baseURL - + "/orgs/" - + this.organizationName - + "/teams?access_token=" - + this.accessToken; - } - - /** - * @param repoName The name of the repository. - * @param teamName The name of the team to set permissions for. - * @return The URL for setting team permissions of a repository. - */ - public String buildTeamPermissionsURL(String repoName, String teamName) { - return baseURL - + "/teams/" - + teamName - + "/repos/" - + this.organizationName - + '/' - + repoName - + "?access_token=" - + this.accessToken; - } - - /** - * @param repoName The name of the repository. - * @return The URL for archiving a repository. - */ - public String buildArchiveRepoURL(String repoName) { - return baseURL - + "/repos/" - + this.organizationName - + '/' - + repoName - + "?access_token=" - + this.accessToken; - } - -} diff --git a/src/main/java/nl/andrewlalis/git_api/package-info.java b/src/main/java/nl/andrewlalis/git_api/package-info.java new file mode 100644 index 0000000..097515a --- /dev/null +++ b/src/main/java/nl/andrewlalis/git_api/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains all logic which directly interacts with the Github API. All methods which need to perform an action on a + * repository or team should find it within this package. + * + * @author Andrew Lalis + */ +package nl.andrewlalis.git_api; \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java index f04723f..918bb5c 100644 --- a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java +++ b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java @@ -1,7 +1,5 @@ package nl.andrewlalis.model; -import org.kohsuke.github.GHTeam; - public class TeachingAssistant extends Person { /** diff --git a/src/main/java/nl/andrewlalis/model/package-info.java b/src/main/java/nl/andrewlalis/model/package-info.java new file mode 100644 index 0000000..d8465c9 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains all objects which form the conceptual basis for this application, such as Students, Teams, and any other + * abstract data containers. + * + * @author Andrew Lalis + */ +package nl.andrewlalis.model; \ No newline at end of file From b7a89c0946e932355cb1443b257ce6f1cb296f3d Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 18 Aug 2018 13:25:18 +0200 Subject: [PATCH 08/19] Started database support. --- src/main/java/nl/andrewlalis/Main.java | 28 +-- .../nl/andrewlalis/database/Database.java | 159 ++++++++++++++++++ .../nl/andrewlalis/git_api/GithubManager.java | 36 ++-- .../java/nl/andrewlalis/model/Student.java | 2 +- .../java/nl/andrewlalis/util/FileUtils.java | 31 ++++ .../java/nl/andrewlalis/util/Logging.java | 18 +- .../resources/sql/insert/insert_person.sql | 2 + .../resources/sql/insert/insert_student.sql | 0 src/main/resources/sql/table_init.sql | 150 +++++++++++++++++ 9 files changed, 394 insertions(+), 32 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/database/Database.java create mode 100644 src/main/java/nl/andrewlalis/util/FileUtils.java create mode 100644 src/main/resources/sql/insert/insert_person.sql create mode 100644 src/main/resources/sql/insert/insert_student.sql create mode 100644 src/main/resources/sql/table_init.sql diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index ce4ea6e..feb9ace 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,20 +1,19 @@ package nl.andrewlalis; +import nl.andrewlalis.database.Database; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.util.CommandLine; +import nl.andrewlalis.util.FileUtils; import nl.andrewlalis.util.Logging; import nl.andrewlalis.util.TeamGenerator; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.Level; import java.util.logging.Logger; -import nl.andrewlalis.util.CommandLine; - /** * Main program entry point. */ @@ -28,16 +27,9 @@ public class Main { Map userOptions = CommandLine.parseArgs(args); // Initialize logger. - ConsoleHandler handler = new ConsoleHandler(); - handler.setLevel(Level.INFO); try { Logging.setup(true); // TODO: Replace true with command line arg. - Handler[] handlers = logger.getHandlers(); - for (Handler h : handlers) { - logger.removeHandler(h); - } - logger.setUseParentHandlers(false); - logger.addHandler(handler); + } catch (IOException e) { logger.severe("Unable to save log to file."); } @@ -62,6 +54,16 @@ public class Main { e.printStackTrace(); } + // Initialize database. + Database db = new Database("database/initializer.sqlite"); + db.initialize(); + for (StudentTeam team : studentTeams) { + for (Student student : team.getStudents()) { + db.storeStudent(student); + } + } + + } /** diff --git a/src/main/java/nl/andrewlalis/database/Database.java b/src/main/java/nl/andrewlalis/database/Database.java new file mode 100644 index 0000000..14a7f1c --- /dev/null +++ b/src/main/java/nl/andrewlalis/database/Database.java @@ -0,0 +1,159 @@ +package nl.andrewlalis.database; + +import nl.andrewlalis.model.Person; +import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.TeachingAssistant; +import nl.andrewlalis.util.FileUtils; + +import java.sql.*; +import java.util.logging.Logger; + +/** + * This class abstracts many of the functions needed for interaction with the application's SQLite database. + */ +public class Database { + + private static final int PERSON_TYPE_STUDENT = 0; + private static final int PERSON_TYPE_TA = 1; + + private static final int TEAM_TYPE_STUDENT = 0; + private static final int TEAM_TYPE_TA = 1; + private static final int TEAM_TYPE_TA_ALL = 2; + + private static final int TEAM_NONE = 0; + private static final int TEAM_TA_ALL = 1; + + private static final int ERROR_TYPE_TEAM = 0; + private static final int ERROR_TYPE_PERSON = 1; + private static final int ERROR_TYPE_SYSTEM = 2; + + /** + * The connection needed for all queries. + */ + private Connection connection; + + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(Database.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + + public Database(String databaseFilename) { + try { + this.connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFilename); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + /** + * Initializes the database from the table_init.sql script, which defines the table schema and starting data. + * @return True, if successful, false if not. + */ + public boolean initialize() { + String sql = FileUtils.readStringFromFile("/sql/table_init.sql"); + String[] commands = sql.split(";"); + for (String command : commands) { + logger.finest("Executing command: " + command); + if (command.trim().length() > 1) { + try { + PreparedStatement statement = this.connection.prepareStatement(command); + statement.execute(); + } catch (SQLException e) { + logger.severe("SQLException: " + e.getErrorCode()); + return false; + } + } + } + logger.fine("Database initialized."); + return true; + } + + /** + * Stores a person in the database. + * @param person The person object to store. + * @param personType The type of person to store, using a constant defined above. + * @return True if successful, false otherwise. + */ + private boolean storePerson(Person person, int personType) { + try { + String sql = "INSERT INTO persons (id, name, email_address, github_username, person_type_id) VALUES (?, ?, ?, ?, ?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, person.getNumber()); + stmt.setString(2, person.getName()); + stmt.setString(3, person.getEmailAddress()); + stmt.setString(4, person.getGithubUsername()); + stmt.setInt(5, personType); + return stmt.execute(); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Stores a teaching assistant without a team. + * @param ta The teaching assistant to store. + * @return True if successful, false otherwise. + */ + public boolean storeTeachingAssistant(TeachingAssistant ta) { + return this.storeTeachingAssistant(ta, TEAM_NONE); + } + + /** + * Stores a teaching assistant in the database. + * @param ta The teaching assistant to store. + * @param teamId The teaching assistant's team id. + * @return True if successful, false otherwise. + */ + public boolean storeTeachingAssistant(TeachingAssistant ta, int teamId) { + if (!storePerson(ta, PERSON_TYPE_TA)) { + return false; + } + try { + String sql = "INSERT INTO teaching_assistants (person_id, team_id) VALUES (?, ?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, ta.getNumber()); + stmt.setInt(2, teamId); + return stmt.execute(); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Stores a student without a team. + * @param student The student to store. + * @return True if successful, false otherwise. + */ + public boolean storeStudent(Student student) { + return this.storeStudent(student, TEAM_NONE); + } + + /** + * Stores a student in the database. + * @param student The student to store. + * @param teamId The team id for the team the student is in. + * @return True if the operation was successful, false otherwise. + */ + public boolean storeStudent(Student student, int teamId) { + if (!storePerson(student, PERSON_TYPE_STUDENT)) { + return false; + } + try { + String sql = "INSERT INTO students (person_id, team_id, chose_partner) VALUES (?, ?, ?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, student.getNumber()); + stmt.setInt(2, teamId); + stmt.setInt(3, student.getPreferredPartners().size() > 0 ? 1 : 0); + return stmt.execute(); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + +} diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 882658c..101d1f1 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -14,7 +14,6 @@ import org.kohsuke.github.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.logging.Logger; /** @@ -206,21 +205,30 @@ public class GithubManager { List repositories = this.organization.listRepositories().asList(); for (GHRepository repo : repositories) { if (repo.getName().contains(sub)) { - HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken); - CloseableHttpClient client = HttpClientBuilder.create().build(); - ObjectMapper mapper = new ObjectMapper(); - ObjectNode root = mapper.createObjectNode(); - root.put("archived", true); - String json = mapper.writeValueAsString(root); - patch.setEntity(new StringEntity(json)); - HttpResponse response = client.execute(patch); - if (response.getStatusLine().getStatusCode() != 200) { - throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode()); - } - logger.info("Archived repository: " + repo.getFullName()); - // TODO: archive repository using Github Java API, instead of Apache HttpUtils. + archiveRepository(repo); } } } + /** + * Archives a repository so that it can no longer be manipulated. + * TODO: Change to using Github API instead of Apache HttpUtils. + * @param repo The repository to archive. + * @throws IOException If an error occurs with the HTTP request. + */ + public void archiveRepository(GHRepository repo) throws IOException { + HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken); + CloseableHttpClient client = HttpClientBuilder.create().build(); + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = mapper.createObjectNode(); + root.put("archived", true); + String json = mapper.writeValueAsString(root); + patch.setEntity(new StringEntity(json)); + HttpResponse response = client.execute(patch); + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode()); + } + logger.info("Archived repository: " + repo.getFullName()); + } + } diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index 1cb0474..321c4e4 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -20,7 +20,7 @@ public class Student extends Person { * @param emailAddress The student's email address. * @param githubUsername The student's github username. * @param preferredPartners A list of this student's preferred partners, as a list of integers representing the - * other students' numbers. + * other students' numbers. */ public Student(int number, String name, String emailAddress, String githubUsername, List preferredPartners) { super(number, name, emailAddress, githubUsername); diff --git a/src/main/java/nl/andrewlalis/util/FileUtils.java b/src/main/java/nl/andrewlalis/util/FileUtils.java new file mode 100644 index 0000000..809d2bd --- /dev/null +++ b/src/main/java/nl/andrewlalis/util/FileUtils.java @@ -0,0 +1,31 @@ +package nl.andrewlalis.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Contains some methods which come in handy in lots of other places. + */ +public class FileUtils { + + /** + * Reads the contents of the file specified by the filename into a String. + * @param filename The filename to read the file of, either relative or absolute. + * @return A string containing the file's contents. + */ + public static String readStringFromFile(String filename) { + try (BufferedReader r = new BufferedReader(new InputStreamReader(FileUtils.class.getResourceAsStream(filename)))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = r.readLine()) != null) { + sb.append(line).append('\n'); + } + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/src/main/java/nl/andrewlalis/util/Logging.java b/src/main/java/nl/andrewlalis/util/Logging.java index 625ef1e..1a021ea 100644 --- a/src/main/java/nl/andrewlalis/util/Logging.java +++ b/src/main/java/nl/andrewlalis/util/Logging.java @@ -14,16 +14,26 @@ public class Logging { public static void setup(boolean verbose) throws IOException { Logger logger = Logger.getGlobal(); - if (verbose) { - logger.setLevel(Level.FINEST); - } else { - logger.setLevel(Level.INFO); + Handler[] handlers = logger.getHandlers(); + for (Handler h : handlers) { + logger.removeHandler(h); } + logger.setUseParentHandlers(false); + + ConsoleHandler handler = new ConsoleHandler(); + if (verbose) { + handler.setLevel(Level.FINEST); + } else { + handler.setLevel(Level.INFO); + } + + logger.addHandler(handler); outputFile = new FileHandler("log/latest.txt"); formatter = new SimpleFormatter(); outputFile.setFormatter(formatter); + outputFile.setLevel(Level.FINEST); logger.addHandler(outputFile); } diff --git a/src/main/resources/sql/insert/insert_person.sql b/src/main/resources/sql/insert/insert_person.sql new file mode 100644 index 0000000..71b6034 --- /dev/null +++ b/src/main/resources/sql/insert/insert_person.sql @@ -0,0 +1,2 @@ +INSERT INTO persons (id, name, email_address, github_username, person_type_id) +VALUES (?, ?, ?, ?, ?); \ No newline at end of file diff --git a/src/main/resources/sql/insert/insert_student.sql b/src/main/resources/sql/insert/insert_student.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/sql/table_init.sql b/src/main/resources/sql/table_init.sql new file mode 100644 index 0000000..efc6cf0 --- /dev/null +++ b/src/main/resources/sql/table_init.sql @@ -0,0 +1,150 @@ +PRAGMA foreign_keys = TRUE; +PRAGMA writable_schema = 1; +DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger'); +PRAGMA writable_schema = 0; +VACUUM; + +-- Basic schema design. +CREATE TABLE IF NOT EXISTS person_types ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE +); + +INSERT INTO person_types (id, name) +VALUES (0, 'student'), + (1, 'teaching-assistant'), + (2, 'professor'); + +CREATE TABLE IF NOT EXISTS persons ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email_address TEXT NOT NULL, + github_username TEXT NOT NULL UNIQUE, + person_type_id INTEGER NOT NULL, + FOREIGN KEY (person_type_id) + REFERENCES person_types(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS team_types ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE +); + +INSERT INTO team_types (id, name) +VALUES (0, 'student_team'), + (1, 'teaching_assistant_team'), + (2, 'all_teaching_assistants'), + (3, 'none'); + +CREATE TABLE IF NOT EXISTS teams ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + team_type_id INTEGER NOT NULL, + FOREIGN KEY (team_type_id) + REFERENCES team_types(id) + ON DELETE CASCADE +); + +INSERT INTO teams (id, team_type_id) +VALUES (0, 3), -- None team for all students or TA's without a team. + (1, 2); -- Team for all teaching assistants. + +CREATE TABLE IF NOT EXISTS teaching_assistant_teams ( + team_id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS student_teams ( + team_id INTEGER PRIMARY KEY, + repository_name TEXT, + group_id INTEGER NOT NULL UNIQUE, + teaching_assistant_team_id INTEGER, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (teaching_assistant_team_id) + REFERENCES teaching_assistant_teams(team_id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS students ( + person_id INTEGER PRIMARY KEY, + team_id INTEGER NOT NULL, + chose_partner INTEGER NOT NULL, + FOREIGN KEY (person_id) + REFERENCES persons(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS teaching_assistants ( + person_id INTEGER PRIMARY KEY, + team_id INTEGER NOT NULL, + FOREIGN KEY (person_id) + REFERENCES persons(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Error queue storage. +CREATE TABLE IF NOT EXISTS error_types ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE +); + +INSERT INTO error_types (id, name) +VALUES (0, 'team_error'), + (1, 'person_error'), + (2, 'system_error'); + +CREATE TABLE IF NOT EXISTS errors ( + id INTEGER PRIMARY KEY, + timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + error_type_id INTEGER NOT NULL, + message TEXT NOT NULL, + FOREIGN KEY (error_type_id) + REFERENCES error_types(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS team_errors ( + error_id INTEGER PRIMARY KEY, + team_id INTEGER NOT NULL, + FOREIGN KEY (error_id) + REFERENCES errors(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS person_errors ( + error_id INTEGER PRIMARY KEY, + person_id INTEGER NOT NULL, + FOREIGN KEY (error_id) + REFERENCES errors(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (person_id) + REFERENCES persons(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); \ No newline at end of file From 54498a4955f3ff177696dffc6e564d02bd620bd1 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 18 Aug 2018 17:48:42 +0200 Subject: [PATCH 09/19] Abstracted team object. --- src/main/java/nl/andrewlalis/Main.java | 3 +- .../nl/andrewlalis/git_api/GithubManager.java | 7 +- .../java/nl/andrewlalis/model/Person.java | 2 +- .../java/nl/andrewlalis/model/Storable.java | 18 +++ .../java/nl/andrewlalis/model/Student.java | 4 +- .../nl/andrewlalis/model/StudentTeam.java | 114 ++----------- src/main/java/nl/andrewlalis/model/Team.java | 153 ++++++++++++++++++ .../{ => model}/database/Database.java | 23 ++- .../nl/andrewlalis/model/database/Utils.java | 52 ++++++ .../nl/andrewlalis/util/TeamGenerator.java | 12 +- 10 files changed, 258 insertions(+), 130 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/model/Storable.java create mode 100644 src/main/java/nl/andrewlalis/model/Team.java rename src/main/java/nl/andrewlalis/{ => model}/database/Database.java (88%) create mode 100644 src/main/java/nl/andrewlalis/model/database/Utils.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index feb9ace..1c35e17 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,11 +1,10 @@ package nl.andrewlalis; -import nl.andrewlalis.database.Database; +import nl.andrewlalis.model.database.Database; import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.util.CommandLine; -import nl.andrewlalis.util.FileUtils; import nl.andrewlalis.util.Logging; import nl.andrewlalis.util.TeamGenerator; diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 101d1f1..d7265a2 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -2,6 +2,7 @@ package nl.andrewlalis.git_api; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import jdk.nashorn.internal.ir.annotations.Ignore; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import org.apache.http.HttpResponse; @@ -88,7 +89,7 @@ public class GithubManager { StudentTeam t = new StudentTeam(); Student s = new Student(3050831, "Andrew Lalis", "andrewlalisofficial@gmail.com", "andrewlalis", null); - t.addStudent(s); + t.addMember(s); t.setId(42); this.setupStudentTeam(t, teamAll); @@ -99,6 +100,7 @@ public class GithubManager { * @param allTeachingAssistants A team consisting of all teaching assistants. * @throws IOException If an HTTP request failed. */ + @SuppressWarnings("deprecation") private void setupAssignmentsRepo(GHTeam allTeachingAssistants) throws IOException { // Check if the repository already exists. GHRepository existingRepo = this.organization.getRepository(this.assignmentsRepoName); @@ -139,10 +141,11 @@ public class GithubManager { * @param taTeam The team of teaching assistants that is responsible for these students. * @throws IOException If an HTTP request fails. */ + @SuppressWarnings("deprecation") private void setupStudentTeam(StudentTeam team, GHTeam taTeam) throws IOException { String teamRepoName = team.generateUniqueName(this.studentRepoPrefix); - List students = team.getStudents(); + Student[] students = team.getStudents(); StringBuilder description = new StringBuilder("Group "); description.append(team.getId()).append(": "); diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java index ea12b84..66b0fb7 100644 --- a/src/main/java/nl/andrewlalis/model/Person.java +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -4,7 +4,7 @@ package nl.andrewlalis.model; * A generic object that students, teaching assistants, and professors can extend from. This covers all the basic * functionality that applies to anyone in the system. */ -public abstract class Person { +public abstract class Person { /** * The unique identification number for this person. (P- or S-Number) diff --git a/src/main/java/nl/andrewlalis/model/Storable.java b/src/main/java/nl/andrewlalis/model/Storable.java new file mode 100644 index 0000000..29a485e --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/Storable.java @@ -0,0 +1,18 @@ +package nl.andrewlalis.model; + +import java.sql.Connection; + +/** + * Defines objects which may be stored in the database, and requires that they implement methods for both storage and + * retrieval of the objects. + */ +public interface Storable { + + /** + * Stores the object in the database. + * @param connection The connection to the database which can be used for preparation of and execution of queries. + * @return True if the object is successfully stored, false if an error occurred. + */ + boolean store(Connection connection); + +} diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index 321c4e4..ec8712b 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -39,9 +39,9 @@ public class Student extends Person { public StudentTeam getPreferredTeam(Map studentMap) { StudentTeam t = new StudentTeam(); for (int partnerNumber : this.getPreferredPartners()) { - t.addStudent(studentMap.get(partnerNumber)); + t.addMember(studentMap.get(partnerNumber)); } - t.addStudent(this); + t.addMember(this); return t; } } diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index d335c80..4243aa2 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -1,74 +1,22 @@ package nl.andrewlalis.model; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; /** * Represents one or more students' collective information. */ -public class StudentTeam { - - /** - * The list of students in this team. - */ - private List students; - - /** - * The team identification number. - */ - private int id; +public class StudentTeam extends Team{ public StudentTeam() { - this.students = new ArrayList<>(); - this.id = -1; + super(-1); } /** - * Determines if a student is already included in this team. - * @param student A student. - * @return True if the student is in this team, false otherwise. + * Gets a list of students, casted from the original Person[]. + * @return An array of Students. */ - public boolean hasStudent(Student student) { - for (Student s : this.students) { - if (s.equals(student)) { - return true; - } - } - return false; - } - - public int getStudentCount() { - return this.students.size(); - } - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public void setStudents(List students) { - this.students = students; - } - - public List getStudents() { - return this.students; - } - - /** - * Adds a student to this team. - * @param student The student to add. - * @return True if the student could be added, false otherwise. - */ - public boolean addStudent(Student student) { - if (!this.hasStudent(student)) { - this.students.add(student); - return true; - } else { - return false; - } + public Student[] getStudents() { + return Arrays.copyOf(this.getMembers(), this.memberCount(), Student[].class); } /** @@ -81,10 +29,9 @@ public class StudentTeam { * @return True if the team is valid, and false otherwise. */ public boolean isValid(int teamSize) { - if (this.getStudentCount() == teamSize) { - List encounteredIds = new ArrayList<>(); - for (Student studentA : this.students) { - for (Student studentB : this.students) { + if (this.memberCount() == teamSize) { + for (Student studentA : this.getStudents()) { + for (Student studentB : this.getStudents()) { if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) { return false; } @@ -105,48 +52,9 @@ public class StudentTeam { public String generateUniqueName(String prefix) { StringBuilder sb = new StringBuilder(prefix); sb.append("_team_").append(this.id); - for (Student s : this.students) { + for (Student s : (Student[]) this.getMembers()) { sb.append('_').append(s.getNumber()); } return sb.toString(); } - - /** - * Returns a pretty formatting of this team so that it can be viewed in the command line. This is mainly for - * debugging purposes. - * @return A string representing the team. - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder("StudentTeam: "); - sb.append(this.id).append('\n'); - for (Student s : this.students) { - sb.append('\t').append(s.toString()).append('\n'); - } - return sb.toString(); - } - - /** - * Determines if one team is equivalent to another. This is determined by if the two teams are comprised of the same - * students, in any order. - * @param o The object to compare to this team. - * @return True if the teams contain the same students, false otherwise. - */ - @Override - public boolean equals(Object o) { - if (o instanceof StudentTeam) { - StudentTeam t = (StudentTeam) o; - if (t.getStudentCount() != this.getStudentCount()) { - return false; - } - for (Student s : this.students) { - if (!t.hasStudent(s)) { - return false; - } - } - return true; - } else { - return false; - } - } } diff --git a/src/main/java/nl/andrewlalis/model/Team.java b/src/main/java/nl/andrewlalis/model/Team.java new file mode 100644 index 0000000..5d5cac4 --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/Team.java @@ -0,0 +1,153 @@ +package nl.andrewlalis.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * An abstract Team object from which both Teaching Assistant and Student teams can be built. + */ +public abstract class Team { + + /** + * An identification number unique to this team alone. + */ + protected int id; + + /** + * A list of members of this team. + */ + private List members; + + /** + * Constructs this team with the given id. + * @param id The id to assign to this team. + */ + public Team(int id) { + this.id = id; + this.members = new ArrayList<>(); + } + + /** + * @param newId The new id number to assign to this team. + */ + public void setId(int newId) { + this.id = newId; + } + + /** + * @return This team's id number. + */ + public int getId() { + return this.id; + } + + /** + * Adds a new person to this team, only if they do not exist in this team yet. + * @param newMember The new member to add. + */ + public void addMember(Person newMember) { + for (Person person : this.members) { + if (person.equals(newMember)) { + return; + } + } + this.members.add(newMember); + } + + /** + * Removes a person from this team. + * @param person The person to remove. + */ + public void removeMember(Person person) { + this.members.remove(person); + } + + /** + * Checks if this team contains the given person. + * @param person The person to check for. + * @return True if the person is a member of this team, false otherwise. + */ + public boolean containsMember(Person person) { + for (Person p : this.members) { + if (p.equals(person)) { + return true; + } + } + return false; + } + + /** + * Sets the team to be comprised of only the members given in the array. + * @param people The people which will make up the members of this team. + */ + public void setMembers(Person[] people) { + this.members = new ArrayList<>(Arrays.asList(people)); + } + + /** + * Gets a list of people in this team. + * @return A list of people in this team. + */ + public Person[] getMembers() { + Person[] people = new Person[this.memberCount()]; + this.members.toArray(people); + return people; + } + + /** + * Gets the number of people in this team. + * @return The number of people in this team. + */ + public int memberCount() { + return this.members.size(); + } + + /** + * Determines if another team has the same members as this team. + * @param team The team to compare to this team. + * @return True if the other team has all the same members as this team. + */ + public boolean hasSameMembers(Team team) { + if (this.memberCount() == team.memberCount()) { + for (Person person : this.members) { + if (!team.containsMember(person)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Checks if an object is equal to this team. First checks if the other object is a Team, and then if it has the + * same id and team size. If both of those conditions are met, then it will check that all team members are the + * same. + * @param obj The object to check for equality. + * @return True if the two objects represent the same team, or false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Team) { + Team team = (Team) obj; + if (team.getId() == this.getId() && this.hasSameMembers(team)) { + return true; + } + } + return false; + } + + /** + * @return A String containing a line for each member in the team. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Person person : this.members) { + sb.append(person.toString()).append('\n'); + } + return sb.toString(); + } + +} diff --git a/src/main/java/nl/andrewlalis/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java similarity index 88% rename from src/main/java/nl/andrewlalis/database/Database.java rename to src/main/java/nl/andrewlalis/model/database/Database.java index 14a7f1c..bd74edc 100644 --- a/src/main/java/nl/andrewlalis/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -1,11 +1,11 @@ -package nl.andrewlalis.database; +package nl.andrewlalis.model.database; import nl.andrewlalis.model.Person; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.TeachingAssistant; -import nl.andrewlalis.util.FileUtils; import java.sql.*; +import java.util.List; import java.util.logging.Logger; /** @@ -53,18 +53,13 @@ public class Database { * @return True, if successful, false if not. */ public boolean initialize() { - String sql = FileUtils.readStringFromFile("/sql/table_init.sql"); - String[] commands = sql.split(";"); - for (String command : commands) { - logger.finest("Executing command: " + command); - if (command.trim().length() > 1) { - try { - PreparedStatement statement = this.connection.prepareStatement(command); - statement.execute(); - } catch (SQLException e) { - logger.severe("SQLException: " + e.getErrorCode()); - return false; - } + List statements = Utils.prepareStatementsFromFile("/sql/table_init.sql", this.connection); + for (PreparedStatement statement : statements) { + try { + statement.execute(); + } catch (SQLException e) { + logger.severe("SQLException while executing prepared statement: " + statement.toString() + ". Code: " + e.getErrorCode()); + return false; } } logger.fine("Database initialized."); diff --git a/src/main/java/nl/andrewlalis/model/database/Utils.java b/src/main/java/nl/andrewlalis/model/database/Utils.java new file mode 100644 index 0000000..2dd006b --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/database/Utils.java @@ -0,0 +1,52 @@ +package nl.andrewlalis.model.database; + +import nl.andrewlalis.util.FileUtils; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * Contains some methods which make database actions much easier. + */ +public class Utils { + + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(Utils.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + + /** + * Gets an ordered list of prepared statements from a file which contains multiple statements separated by a + * semicolon. This method separates those statements into their own strings, and prepares them individually to be + * executed later. + * @param filename The name of the file which contains the statements. + * @param connection The connection to a database; used to prepare statements. + * @return An ordered list of prepared statements which are based on the contents of the file provided. + */ + public static List prepareStatementsFromFile(String filename, Connection connection) { + String string = FileUtils.readStringFromFile(filename); + if (string == null || string.isEmpty()) { + return new ArrayList<>(); + } + String[] splits = string.split(";"); + List statements = new ArrayList<>(); + for (String split : splits) { + if (split.trim().length() > 1) { + try { + statements.add(connection.prepareStatement(split)); + } catch (SQLException e) { + logger.severe("SQLException while preparing a statement:\n" + split + "\nError Code: " + e.getErrorCode()); + } + } + } + return statements; + } + +} diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 44cc264..2057b71 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -80,15 +80,15 @@ public class TeamGenerator { // For each student, try to make a team from its preferred partners. for (Map.Entry e : studentMap.entrySet()) { StudentTeam newTeam = e.getValue().getPreferredTeam(studentMap); - logger.finest("Checking if student's preferred team is valid: " + newTeam.getStudents()); + logger.finest("Checking if student's preferred team is valid:\n" + newTeam); // Check if the team is of a valid size, and is not a duplicate. // Note that at this stage, singles are treated as studentTeams of 1, and thus not valid for any teamSize > 1. if (newTeam.isValid(teamSize) && !studentTeams.contains(newTeam)) { // Once we know this team is completely valid, we remove all the students in it from the list of singles. newTeam.setId(teamCount++); - singleStudents.removeAll(newTeam.getStudents()); + singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); studentTeams.add(newTeam); - logger.fine("Created team: " + newTeam); + logger.fine("Created team:\n" + newTeam); } } @@ -109,11 +109,11 @@ public class TeamGenerator { while (!singleStudents.isEmpty()) { StudentTeam t = new StudentTeam(); t.setId(teamIndex++); - logger.fine("Creating new team of single students: " + t); - while (t.getStudentCount() < teamSize && !singleStudents.isEmpty()) { + logger.fine("Creating new team of single students:\n" + t); + while (t.memberCount() < teamSize && !singleStudents.isEmpty()) { Student s = singleStudents.remove(0); logger.finest("Single student: " + s); - t.addStudent(s); + t.addMember(s); } studentTeams.add(t); logger.fine("Created team: " + t); From 13052772078ad54c59f8c669e1f1fa856fbb558a Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 18 Aug 2018 18:26:20 +0200 Subject: [PATCH 10/19] Re-worked database to be more robust with new types. --- src/main/java/nl/andrewlalis/Main.java | 3 ++- src/main/java/nl/andrewlalis/model/Team.java | 8 +++--- .../andrewlalis/model/database/Database.java | 21 +++++++++++---- .../nl/andrewlalis/model/database/Utils.java | 2 +- .../java/nl/andrewlalis/util/Logging.java | 23 ++++++---------- .../nl/andrewlalis/util/TeamGenerator.java | 26 +++++++++++++------ .../resources/sql/insert/insert_person.sql | 2 -- .../resources/sql/insert/insert_student.sql | 0 src/main/resources/sql/insert/types.sql | 19 ++++++++++++++ src/main/resources/sql/table_init.sql | 20 -------------- 10 files changed, 68 insertions(+), 56 deletions(-) delete mode 100644 src/main/resources/sql/insert/insert_person.sql delete mode 100644 src/main/resources/sql/insert/insert_student.sql create mode 100644 src/main/resources/sql/insert/types.sql diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 1c35e17..f00ca3c 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -11,6 +11,7 @@ import nl.andrewlalis.util.TeamGenerator; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -75,7 +76,7 @@ public class Main { List studentTeams = null; try { studentTeams = TeamGenerator.generateFromCSV(filename, teamSize); - logger.info("Teams created: " + studentTeams); + logger.fine("Teams created:\n" + studentTeams); return studentTeams; } catch (IOException | ArrayIndexOutOfBoundsException e) { logger.severe("Unable to generate studentTeams from CSV file, exiting."); diff --git a/src/main/java/nl/andrewlalis/model/Team.java b/src/main/java/nl/andrewlalis/model/Team.java index 5d5cac4..f1b23db 100644 --- a/src/main/java/nl/andrewlalis/model/Team.java +++ b/src/main/java/nl/andrewlalis/model/Team.java @@ -5,7 +5,8 @@ import java.util.Arrays; import java.util.List; /** - * An abstract Team object from which both Teaching Assistant and Student teams can be built. + * An abstract Team object from which both Teaching Assistant and Student teams can be built. A Team consists of a list + * of members, and a unique identification number. */ public abstract class Team { @@ -131,9 +132,7 @@ public abstract class Team { public boolean equals(Object obj) { if (obj instanceof Team) { Team team = (Team) obj; - if (team.getId() == this.getId() && this.hasSameMembers(team)) { - return true; - } + return team.getId() == this.getId() && this.hasSameMembers(team); } return false; } @@ -144,6 +143,7 @@ public abstract class Team { @Override public String toString() { StringBuilder sb = new StringBuilder(); + sb.append("Team of ").append(this.memberCount()).append(" members:\tID: ").append(this.id).append('\n'); for (Person person : this.members) { sb.append(person.toString()).append('\n'); } diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index bd74edc..e7c33e9 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -49,12 +49,13 @@ public class Database { } /** - * Initializes the database from the table_init.sql script, which defines the table schema and starting data. - * @return True, if successful, false if not. + * Initializes the database from the table_init.sql script, which defines the table schema. + * Then, inserts some constant starter data from /sql/insert/types.sql. + * @return True if successful, false if not. */ public boolean initialize() { - List statements = Utils.prepareStatementsFromFile("/sql/table_init.sql", this.connection); - for (PreparedStatement statement : statements) { + List tableStatements = Utils.prepareStatementsFromFile("/sql/table_init.sql", this.connection); + for (PreparedStatement statement : tableStatements) { try { statement.execute(); } catch (SQLException e) { @@ -62,7 +63,17 @@ public class Database { return false; } } - logger.fine("Database initialized."); + logger.fine("Database tables initialized."); + List insertStatements = Utils.prepareStatementsFromFile("/sql/insert/types.sql", this.connection); + for (PreparedStatement statement : insertStatements) { + try { + statement.execute(); + } catch (SQLException e) { + logger.severe("SQLException while inserting into table: " + statement.toString() + ". Code: " + e.getErrorCode()); + return false; + } + } + logger.fine("Initial types inserted."); return true; } diff --git a/src/main/java/nl/andrewlalis/model/database/Utils.java b/src/main/java/nl/andrewlalis/model/database/Utils.java index 2dd006b..e908f1d 100644 --- a/src/main/java/nl/andrewlalis/model/database/Utils.java +++ b/src/main/java/nl/andrewlalis/model/database/Utils.java @@ -42,7 +42,7 @@ public class Utils { try { statements.add(connection.prepareStatement(split)); } catch (SQLException e) { - logger.severe("SQLException while preparing a statement:\n" + split + "\nError Code: " + e.getErrorCode()); + logger.severe("SQLException while preparing a statement:\n" + split + "\nError Code: " + e.getErrorCode() + '\n' + e.getMessage()); } } } diff --git a/src/main/java/nl/andrewlalis/util/Logging.java b/src/main/java/nl/andrewlalis/util/Logging.java index 1a021ea..e8e4124 100644 --- a/src/main/java/nl/andrewlalis/util/Logging.java +++ b/src/main/java/nl/andrewlalis/util/Logging.java @@ -14,27 +14,20 @@ public class Logging { public static void setup(boolean verbose) throws IOException { Logger logger = Logger.getGlobal(); - Handler[] handlers = logger.getHandlers(); - for (Handler h : handlers) { - logger.removeHandler(h); - } - logger.setUseParentHandlers(false); - - ConsoleHandler handler = new ConsoleHandler(); - if (verbose) { - handler.setLevel(Level.FINEST); - } else { - handler.setLevel(Level.INFO); - } - - logger.addHandler(handler); - outputFile = new FileHandler("log/latest.txt"); formatter = new SimpleFormatter(); outputFile.setFormatter(formatter); outputFile.setLevel(Level.FINEST); + + Handler systemOut = new ConsoleHandler(); + systemOut.setLevel(Level.ALL); + + //logger.addHandler(systemOut); logger.addHandler(outputFile); + logger.setLevel(Level.ALL); + + Logger.getLogger("").setLevel(Level.OFF); } diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 2057b71..b765e7b 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -33,7 +33,7 @@ public class TeamGenerator { * @throws IllegalArgumentException If an invalid teamsize is given. */ public static List generateFromCSV(String filename, int teamSize) throws IOException, IllegalArgumentException { - logger.info("Generating teams of size " + teamSize); + logger.fine("Generating teams of size " + teamSize); if (teamSize < 1) { logger.severe("Invalid team size."); throw new IllegalArgumentException("StudentTeam size must be greater than or equal to 1. Got " + teamSize); @@ -83,12 +83,22 @@ public class TeamGenerator { logger.finest("Checking if student's preferred team is valid:\n" + newTeam); // Check if the team is of a valid size, and is not a duplicate. // Note that at this stage, singles are treated as studentTeams of 1, and thus not valid for any teamSize > 1. - if (newTeam.isValid(teamSize) && !studentTeams.contains(newTeam)) { - // Once we know this team is completely valid, we remove all the students in it from the list of singles. - newTeam.setId(teamCount++); - singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); - studentTeams.add(newTeam); - logger.fine("Created team:\n" + newTeam); + if (newTeam.isValid(teamSize)) { + // We know that the team is valid on its own, so now we check if it has members identical to any team already created. + boolean matchFound = false; + for (StudentTeam team : studentTeams) { + if (newTeam.hasSameMembers(team)) { + matchFound = true; + break; + } + } + if (!matchFound) { + // Once we know this team is completely valid, we remove all the students in it from the list of singles. + newTeam.setId(teamCount++); + singleStudents.removeAll(Arrays.asList(newTeam.getStudents())); + studentTeams.add(newTeam); + logger.fine("Created team:\n" + newTeam); + } } } @@ -116,7 +126,7 @@ public class TeamGenerator { t.addMember(s); } studentTeams.add(t); - logger.fine("Created team: " + t); + logger.fine("Created team:\n" + t); } return studentTeams; } diff --git a/src/main/resources/sql/insert/insert_person.sql b/src/main/resources/sql/insert/insert_person.sql deleted file mode 100644 index 71b6034..0000000 --- a/src/main/resources/sql/insert/insert_person.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO persons (id, name, email_address, github_username, person_type_id) -VALUES (?, ?, ?, ?, ?); \ No newline at end of file diff --git a/src/main/resources/sql/insert/insert_student.sql b/src/main/resources/sql/insert/insert_student.sql deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/sql/insert/types.sql b/src/main/resources/sql/insert/types.sql new file mode 100644 index 0000000..45ab89d --- /dev/null +++ b/src/main/resources/sql/insert/types.sql @@ -0,0 +1,19 @@ +INSERT INTO person_types (id, name) +VALUES (0, 'student'), + (1, 'teaching-assistant'), + (2, 'professor'); + +INSERT INTO team_types (id, name) +VALUES (0, 'student_team'), + (1, 'teaching_assistant_team'), + (2, 'all_teaching_assistants'), + (3, 'none'); + +INSERT INTO teams (id, team_type_id) +VALUES (0, 3), -- None team for all students or TA's without a team. + (1, 2); -- Team for all teaching assistants. + +INSERT INTO error_types (id, name) +VALUES (0, 'team_error'), + (1, 'person_error'), + (2, 'system_error'); \ No newline at end of file diff --git a/src/main/resources/sql/table_init.sql b/src/main/resources/sql/table_init.sql index efc6cf0..2604917 100644 --- a/src/main/resources/sql/table_init.sql +++ b/src/main/resources/sql/table_init.sql @@ -10,11 +10,6 @@ CREATE TABLE IF NOT EXISTS person_types ( name TEXT NOT NULL UNIQUE ); -INSERT INTO person_types (id, name) -VALUES (0, 'student'), - (1, 'teaching-assistant'), - (2, 'professor'); - CREATE TABLE IF NOT EXISTS persons ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, @@ -32,12 +27,6 @@ CREATE TABLE IF NOT EXISTS team_types ( name TEXT NOT NULL UNIQUE ); -INSERT INTO team_types (id, name) -VALUES (0, 'student_team'), - (1, 'teaching_assistant_team'), - (2, 'all_teaching_assistants'), - (3, 'none'); - CREATE TABLE IF NOT EXISTS teams ( id INTEGER PRIMARY KEY AUTOINCREMENT, team_type_id INTEGER NOT NULL, @@ -46,10 +35,6 @@ CREATE TABLE IF NOT EXISTS teams ( ON DELETE CASCADE ); -INSERT INTO teams (id, team_type_id) -VALUES (0, 3), -- None team for all students or TA's without a team. - (1, 2); -- Team for all teaching assistants. - CREATE TABLE IF NOT EXISTS teaching_assistant_teams ( team_id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, @@ -107,11 +92,6 @@ CREATE TABLE IF NOT EXISTS error_types ( name TEXT NOT NULL UNIQUE ); -INSERT INTO error_types (id, name) -VALUES (0, 'team_error'), - (1, 'person_error'), - (2, 'system_error'); - CREATE TABLE IF NOT EXISTS errors ( id INTEGER PRIMARY KEY, timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, From 3ea182fb4c12637474426158f42b2fb47643612a Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 18 Aug 2018 18:29:13 +0200 Subject: [PATCH 11/19] Fixed return issues with store functions. --- src/main/java/nl/andrewlalis/model/database/Database.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index e7c33e9..c4da185 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -123,7 +123,8 @@ public class Database { PreparedStatement stmt = this.connection.prepareStatement(sql); stmt.setInt(1, ta.getNumber()); stmt.setInt(2, teamId); - return stmt.execute(); + stmt.execute(); + return true; } catch (SQLException e) { e.printStackTrace(); return false; @@ -155,7 +156,8 @@ public class Database { stmt.setInt(1, student.getNumber()); stmt.setInt(2, teamId); stmt.setInt(3, student.getPreferredPartners().size() > 0 ? 1 : 0); - return stmt.execute(); + stmt.execute(); + return true; } catch (SQLException e) { e.printStackTrace(); return false; From eb0e70a3841af6ba381c48cd13cf0ae6b67e570a Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Fri, 24 Aug 2018 16:31:44 +0200 Subject: [PATCH 12/19] Added styled messages. --- src/main/java/nl/andrewlalis/Main.java | 56 ++++------ .../java/nl/andrewlalis/model/Storable.java | 18 ---- .../andrewlalis/model/database/Database.java | 28 ++++- .../ui/control/CommandExecutor.java | 10 ++ .../ui/control/CommandFieldKeyListener.java | 24 +++++ .../ui/control/OutputTextHandler.java | 42 ++++++++ .../andrewlalis/ui/view/InitializerApp.java | 102 ++++++++++++++++++ .../andrewlalis/ui/view/OutputTextPane.java | 83 ++++++++++++++ .../java/nl/andrewlalis/util/FileUtils.java | 31 ++++++ .../java/nl/andrewlalis/util/Logging.java | 8 +- 10 files changed, 345 insertions(+), 57 deletions(-) delete mode 100644 src/main/java/nl/andrewlalis/model/Storable.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/InitializerApp.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index f00ca3c..3545fe7 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -4,10 +4,12 @@ import nl.andrewlalis.model.database.Database; import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; import nl.andrewlalis.util.TeamGenerator; +import javax.swing.*; import java.io.IOException; import java.util.List; import java.util.Map; @@ -34,18 +36,21 @@ public class Main { logger.severe("Unable to save log to file."); } + // Initialize User Interface. + InitializerApp app = new InitializerApp(); + logger.info("GithubManager for Github Repositories in Educational Organizations."); // Get studentTeams from CSV file. - List studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize"))); - - GithubManager githubManager = new GithubManager( - userOptions.get("organization"), - userOptions.get("token"), - "assignments_2018", - "teaching-assistants", - "advoop_2018" - ); +// List studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize"))); +// +// GithubManager githubManager = new GithubManager( +// userOptions.get("organization"), +// userOptions.get("token"), +// "assignments_2018", +// "teaching-assistants", +// "advoop_2018" +// ); try { //githubManager.initializeGithubRepos(studentTeams); @@ -55,34 +60,17 @@ public class Main { } // Initialize database. - Database db = new Database("database/initializer.sqlite"); - db.initialize(); - for (StudentTeam team : studentTeams) { - for (Student student : team.getStudents()) { - db.storeStudent(student); - } - } +// Database db = new Database("database/initializer.sqlite"); +// db.initialize(); +// for (StudentTeam team : studentTeams) { +// for (Student student : team.getStudents()) { +// db.storeStudent(student); +// } +// } } - /** - * Reads a list of students from a CSV file and compiles a list of teams based on their preferred partners. - * @param filename The name of the CSV file. - * @param teamSize The intended size of teams. - * @return A list of student teams. - */ - private static List getStudentTeamsFromCSV(String filename, int teamSize) { - List studentTeams = null; - try { - studentTeams = TeamGenerator.generateFromCSV(filename, teamSize); - logger.fine("Teams created:\n" + studentTeams); - return studentTeams; - } catch (IOException | ArrayIndexOutOfBoundsException e) { - logger.severe("Unable to generate studentTeams from CSV file, exiting."); - System.exit(1); - return null; - } - } + } diff --git a/src/main/java/nl/andrewlalis/model/Storable.java b/src/main/java/nl/andrewlalis/model/Storable.java deleted file mode 100644 index 29a485e..0000000 --- a/src/main/java/nl/andrewlalis/model/Storable.java +++ /dev/null @@ -1,18 +0,0 @@ -package nl.andrewlalis.model; - -import java.sql.Connection; - -/** - * Defines objects which may be stored in the database, and requires that they implement methods for both storage and - * retrieval of the objects. - */ -public interface Storable { - - /** - * Stores the object in the database. - * @param connection The connection to the database which can be used for preparation of and execution of queries. - * @return True if the object is successfully stored, false if an error occurred. - */ - boolean store(Connection connection); - -} diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index c4da185..33fb282 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -3,6 +3,7 @@ package nl.andrewlalis.model.database; import nl.andrewlalis.model.Person; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.TeachingAssistant; +import nl.andrewlalis.model.Team; import java.sql.*; import java.util.List; @@ -85,6 +86,7 @@ public class Database { */ private boolean storePerson(Person person, int personType) { try { + logger.finest("Storing person: " + person); String sql = "INSERT INTO persons (id, name, email_address, github_username, person_type_id) VALUES (?, ?, ?, ?, ?);"; PreparedStatement stmt = this.connection.prepareStatement(sql); stmt.setInt(1, person.getNumber()); @@ -92,9 +94,10 @@ public class Database { stmt.setString(3, person.getEmailAddress()); stmt.setString(4, person.getGithubUsername()); stmt.setInt(5, personType); - return stmt.execute(); + stmt.execute(); + return true; } catch (SQLException e) { - e.printStackTrace(); + logger.severe("SQLException while inserting Person: " + person + '\n' + e.getMessage()); return false; } } @@ -131,6 +134,26 @@ public class Database { } } + /** + * Stores a team in the database. + * @param team The team to store. + * @param type The type of team that this is. + * @return True if successful, false otherwise. + */ + public boolean storeTeam(Team team, int type) { + try { + String sql = "INSERT INTO teams (id, team_type_id) VALUES (?, ?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, team.getId()); + stmt.setInt(2, type); + stmt.execute(); + return true; + } catch (SQLException e) { + logger.severe("SQLException while inserting team: " + team + '\n' + e.getMessage()); + return false; + } + } + /** * Stores a student without a team. * @param student The student to store. @@ -147,6 +170,7 @@ public class Database { * @return True if the operation was successful, false otherwise. */ public boolean storeStudent(Student student, int teamId) { + logger.finest("Storing student: " + student); if (!storePerson(student, PERSON_TYPE_STUDENT)) { return false; } diff --git a/src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java b/src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java new file mode 100644 index 0000000..2d52f50 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java @@ -0,0 +1,10 @@ +package nl.andrewlalis.ui.control; + +/** + * Manages parsing an entered string and executing a task based upon information in the command. + */ +public class CommandExecutor { + + + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java b/src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java new file mode 100644 index 0000000..f0b170f --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java @@ -0,0 +1,24 @@ +package nl.andrewlalis.ui.control; + +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +public class CommandFieldKeyListener implements KeyListener { + + @Override + public void keyTyped(KeyEvent keyEvent) { + + } + + @Override + public void keyPressed(KeyEvent keyEvent) { + + } + + @Override + public void keyReleased(KeyEvent keyEvent) { + if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) { + System.out.println("Enter pressed."); + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java b/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java new file mode 100644 index 0000000..a4d6710 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java @@ -0,0 +1,42 @@ +package nl.andrewlalis.ui.control; + +import nl.andrewlalis.ui.view.OutputTextPane; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class OutputTextHandler extends Handler { + + /** + * The pane to which this handler writes. + */ + private OutputTextPane outputPane; + + public OutputTextHandler(OutputTextPane outputPane) { + this.outputPane = outputPane; + } + + @Override + public void publish(LogRecord logRecord) { + DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + String dateString = df.format(new Date(logRecord.getMillis())); + String sourceLocationString = logRecord.getSourceClassName() + "::" + logRecord.getSourceMethodName(); + this.outputPane.printStyled(dateString + ' ', "gray_italics"); + this.outputPane.printStyled(logRecord.getLevel().getName() + ": ", "bold"); + this.outputPane.printStyled(sourceLocationString + "\n\t", "bold"); + this.outputPane.printStyled(logRecord.getMessage() + '\n', "smaller"); + } + + @Override + public void flush() { + + } + + @Override + public void close() throws SecurityException { + + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java new file mode 100644 index 0000000..3eef11f --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -0,0 +1,102 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.ui.control.CommandFieldKeyListener; +import nl.andrewlalis.ui.control.OutputTextHandler; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.logging.Logger; + +/** + * Represents the main user interface element that is referenced in main to construct the graphic interface. + */ +public class InitializerApp extends JFrame { + + /** + * The window title. + */ + private static final String FRAME_TITLE = "Github Initializer"; + + /** + * A default size of the window on startup. + */ + private static final Dimension SIZE = new Dimension(800, 600); + + private OutputTextPane outputTextPane; + + public InitializerApp() { + this.initFrame(); + } + + public void printMessage() { + + } + + /** + * Initializes the handler which passes logging information to the text pane for display. + */ + private void initLoggingHandler() { + Logger logger = Logger.getGlobal(); + logger.addHandler(new OutputTextHandler(this.outputTextPane)); + } + + /** + * Initializes the frame before display should begin. + */ + private void initFrame() { + this.setTitle(FRAME_TITLE); + this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + this.setPreferredSize(SIZE); + + JPanel mainPanel = new JPanel(new BorderLayout()); + + mainPanel.add(this.initCommandPanel(), BorderLayout.CENTER); + mainPanel.add(this.initRepoPanel(), BorderLayout.WEST); + + this.setContentPane(mainPanel); + + this.pack(); + this.initLoggingHandler(); + this.setVisible(true); + } + + /** + * @return A JPanel containing the command prompt field and output text pane. + */ + private JPanel initCommandPanel() { + JPanel commandPanel = new JPanel(new BorderLayout()); + // Output text pane. + this.outputTextPane = new OutputTextPane(); + JScrollPane scrollPane = new JScrollPane(this.outputTextPane); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + // Text enter field and label. + JPanel textEnterPanel = new JPanel(new BorderLayout()); + textEnterPanel.setBorder(BorderFactory.createLoweredBevelBorder()); + textEnterPanel.add(new JLabel("Command:"), BorderLayout.WEST); + JTextField commandField = new JTextField(); + commandField.addKeyListener(new CommandFieldKeyListener()); + textEnterPanel.add(commandField, BorderLayout.CENTER); + // Top Label + JLabel commandPanelLabel = new JLabel("Program output", SwingConstants.CENTER); + + commandPanel.add(scrollPane, BorderLayout.CENTER); + commandPanel.add(textEnterPanel, BorderLayout.SOUTH); + commandPanel.add(commandPanelLabel, BorderLayout.NORTH); + return commandPanel; + } + + /** + * @return A JPanel containing a list of repositories. + */ + private JPanel initRepoPanel() { + JPanel repoPanel = new JPanel(); + repoPanel.setLayout(new BoxLayout(repoPanel, BoxLayout.PAGE_AXIS)); + repoPanel.add(new JLabel("Repositories")); + repoPanel.add(new JList()); + return repoPanel; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java b/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java new file mode 100644 index 0000000..64569cd --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java @@ -0,0 +1,83 @@ +package nl.andrewlalis.ui.view; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +/** + * A custom JTextPane object which provides easy methods to print to the screen. + */ +public class OutputTextPane extends JTextPane { + + /** + * A list of named styles. + */ + private Map styles; + + /** + * A basic constructor to set default properties on this text pane. + */ + public OutputTextPane() { + this.initStyles(); + this.setEditable(false); + this.setAutoscrolls(true); + } + + private void initStyles() { + this.styles = new HashMap<>(); + Style defaultStyle = this.addStyle("default", null); + defaultStyle.addAttribute(StyleConstants.FontFamily, "Lucida Consonle"); + defaultStyle.addAttribute(StyleConstants.FontSize, 12); + this.styles.put("default", defaultStyle); + + Style grayItalics = this.addStyle("gray_italics", defaultStyle); + grayItalics.addAttribute(StyleConstants.Foreground, new Color(128, 128, 128)); + grayItalics.addAttribute(StyleConstants.Italic, true); + this.styles.put("gray_italics", grayItalics); + + Style bold = this.addStyle("bold", defaultStyle); + bold.addAttribute(StyleConstants.Bold, true); + this.styles.put("bold", bold); + + Style smaller = this.addStyle("smaller", defaultStyle); + smaller.addAttribute(StyleConstants.FontSize, 11); + this.styles.put("smaller", smaller); + } + + /** + * Prints some text styled with a style that is defined in initStyles. + * @param text The text to display. + * @param styleName The name of the style to use. + */ + public void printStyled(String text, String styleName) { + StyledDocument doc = this.getStyledDocument(); + Style style = this.styles.get(styleName); + if (style == null) { + style = this.styles.get("default"); + } + try { + doc.insertString(doc.getLength(), text, style); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + /** + * Prints a line of text, with a newline character appended at the bottom. + * @param text The text to append. + */ + public void printLine(String text) { + StyledDocument doc = this.getStyledDocument(); + try { + doc.insertString(doc.getLength(), (text + '\n'), null); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/nl/andrewlalis/util/FileUtils.java b/src/main/java/nl/andrewlalis/util/FileUtils.java index 809d2bd..f5cf3db 100644 --- a/src/main/java/nl/andrewlalis/util/FileUtils.java +++ b/src/main/java/nl/andrewlalis/util/FileUtils.java @@ -1,14 +1,26 @@ package nl.andrewlalis.util; +import nl.andrewlalis.model.StudentTeam; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.List; +import java.util.logging.Logger; /** * Contains some methods which come in handy in lots of other places. */ public class FileUtils { + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(FileUtils.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + /** * Reads the contents of the file specified by the filename into a String. * @param filename The filename to read the file of, either relative or absolute. @@ -28,4 +40,23 @@ public class FileUtils { } } + /** + * Reads a list of students from a CSV file and compiles a list of teams based on their preferred partners. + * @param filename The name of the CSV file. + * @param teamSize The intended size of teams. + * @return A list of student teams. + */ + public static List getStudentTeamsFromCSV(String filename, int teamSize) { + List studentTeams = null; + try { + studentTeams = TeamGenerator.generateFromCSV(filename, teamSize); + logger.fine("Teams created:\n" + studentTeams); + return studentTeams; + } catch (IOException | ArrayIndexOutOfBoundsException e) { + logger.severe("Unable to generate studentTeams from CSV file, exiting."); + System.exit(1); + return null; + } + } + } diff --git a/src/main/java/nl/andrewlalis/util/Logging.java b/src/main/java/nl/andrewlalis/util/Logging.java index e8e4124..b9b7113 100644 --- a/src/main/java/nl/andrewlalis/util/Logging.java +++ b/src/main/java/nl/andrewlalis/util/Logging.java @@ -20,10 +20,12 @@ public class Logging { outputFile.setFormatter(formatter); outputFile.setLevel(Level.FINEST); - Handler systemOut = new ConsoleHandler(); - systemOut.setLevel(Level.ALL); + if (verbose) { + Handler systemOut = new ConsoleHandler(); + systemOut.setLevel(Level.ALL); - //logger.addHandler(systemOut); + logger.addHandler(systemOut); + } logger.addHandler(outputFile); logger.setLevel(Level.ALL); From a947e081658a27a923853286de63cac2b975b92c Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Fri, 24 Aug 2018 17:08:05 +0200 Subject: [PATCH 13/19] Added basic command executor functionality. --- src/main/java/nl/andrewlalis/Main.java | 12 +++- .../andrewlalis/model/database/Database.java | 5 +- .../ui/control/CommandExecutor.java | 10 --- .../ui/control/CommandFieldKeyListener.java | 24 -------- .../ui/control/command/CommandExecutor.java | 61 +++++++++++++++++++ .../ui/control/command/Executable.java | 17 ++++++ .../listeners/CommandFieldKeyListener.java | 42 +++++++++++++ .../andrewlalis/ui/view/InitializerApp.java | 30 ++++++--- .../java/nl/andrewlalis/util/Logging.java | 2 +- 9 files changed, 157 insertions(+), 46 deletions(-) delete mode 100644 src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java delete mode 100644 src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/Executable.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 3545fe7..e2aee76 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -4,6 +4,8 @@ import nl.andrewlalis.model.database.Database; import nl.andrewlalis.git_api.GithubManager; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.control.command.Executable; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; @@ -36,8 +38,16 @@ public class Main { logger.severe("Unable to save log to file."); } + CommandExecutor executor = new CommandExecutor(); + executor.registerCommand("test", args1 -> { + System.out.println("TESTING"); + return true; + }); + // Initialize User Interface. - InitializerApp app = new InitializerApp(); + InitializerApp app = new InitializerApp(executor); + + app.begin(); logger.info("GithubManager for Github Repositories in Educational Organizations."); diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index 33fb282..311dde3 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -5,7 +5,10 @@ import nl.andrewlalis.model.Student; import nl.andrewlalis.model.TeachingAssistant; import nl.andrewlalis.model.Team; -import java.sql.*; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.List; import java.util.logging.Logger; diff --git a/src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java b/src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java deleted file mode 100644 index 2d52f50..0000000 --- a/src/main/java/nl/andrewlalis/ui/control/CommandExecutor.java +++ /dev/null @@ -1,10 +0,0 @@ -package nl.andrewlalis.ui.control; - -/** - * Manages parsing an entered string and executing a task based upon information in the command. - */ -public class CommandExecutor { - - - -} diff --git a/src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java b/src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java deleted file mode 100644 index f0b170f..0000000 --- a/src/main/java/nl/andrewlalis/ui/control/CommandFieldKeyListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.andrewlalis.ui.control; - -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; - -public class CommandFieldKeyListener implements KeyListener { - - @Override - public void keyTyped(KeyEvent keyEvent) { - - } - - @Override - public void keyPressed(KeyEvent keyEvent) { - - } - - @Override - public void keyReleased(KeyEvent keyEvent) { - if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) { - System.out.println("Enter pressed."); - } - } -} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java new file mode 100644 index 0000000..222bfb3 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java @@ -0,0 +1,61 @@ +package nl.andrewlalis.ui.control.command; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * Manages parsing an entered string and executing a task based upon information in the command. + */ +public class CommandExecutor { + + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(CommandExecutor.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + + /** + * A list of named commands which can be executed. + */ + private Map commands; + + public CommandExecutor() { + this.commands = new HashMap<>(); + } + + /** + * Adds a new command to the list of commands which this executor can handle. + * @param commandName The name that the command will be found by. + * @param executable The executable command that is bound to the given name. + */ + public void registerCommand(String commandName, Executable executable) { + this.commands.put(commandName, executable); + } + + /** + * Attempts to execute a command string, or show an error message if an invalid command or argument was entered. + * @param commandString The String command and any arguments that go with it. + */ + public void executeString(String commandString) { + String[] words = commandString.trim().toLowerCase().split(" "); + if (words.length < 1) { + logger.warning("No command supplied."); + return; + } + String commandName = words[0]; + String[] args = new String[words.length - 1]; + if (words.length > 1) { + System.arraycopy(words, 1, args, 0, words.length - 1); + } + if (this.commands.containsKey(commandName)) { + this.commands.get(commandName).execute(args); + logger.info(commandString); + } else { + logger.warning(commandName + " is not a valid command."); + } + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/Executable.java b/src/main/java/nl/andrewlalis/ui/control/command/Executable.java new file mode 100644 index 0000000..77ed4bd --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/Executable.java @@ -0,0 +1,17 @@ +package nl.andrewlalis.ui.control.command; + +/** + * Classes which implement this interface tell that they may be 'executed', either via command-line, or through the use + * of user interface actions. + */ +public interface Executable { + + /** + * Runs this Executable's main functionality. + * + * @param args The list of arguments supplied to the executable. + * @return True if successful, false if an error occurred. + */ + boolean execute(String[] args); + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java new file mode 100644 index 0000000..a90f909 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java @@ -0,0 +1,42 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.control.command.CommandExecutor; + +import javax.swing.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +/** + * This Key Listener listens for when the ENTER key is pressed in the command-line text field, and executes the command + * when that is the case. + */ +public class CommandFieldKeyListener implements KeyListener { + + /** + * This is responsible for parsing and running entered commands. + */ + CommandExecutor executor; + + public CommandFieldKeyListener(CommandExecutor executor) { + this.executor = executor; + } + + @Override + public void keyTyped(KeyEvent keyEvent) { + + } + + @Override + public void keyPressed(KeyEvent keyEvent) { + + } + + @Override + public void keyReleased(KeyEvent keyEvent) { + if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) { + JTextField inputField = (JTextField) keyEvent.getComponent(); + this.executor.executeString(inputField.getText()); + inputField.setText(null); + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 3eef11f..7a3b614 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -1,12 +1,11 @@ package nl.andrewlalis.ui.view; -import nl.andrewlalis.ui.control.CommandFieldKeyListener; +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener; import nl.andrewlalis.ui.control.OutputTextHandler; import javax.swing.*; import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; import java.util.logging.Logger; /** @@ -24,14 +23,29 @@ public class InitializerApp extends JFrame { */ private static final Dimension SIZE = new Dimension(800, 600); + /** + * The pane on which general purpose program output is written. + */ private OutputTextPane outputTextPane; - public InitializerApp() { + /** + * The executor responsible for performing meaningful actions. + */ + private CommandExecutor executor; + + public InitializerApp(CommandExecutor executor) { + this.executor = executor; + + // UI initialization. this.initFrame(); } - public void printMessage() { - + /** + * Begins showing the application + */ + public void begin() { + this.pack(); + this.setVisible(true); } /** @@ -57,9 +71,7 @@ public class InitializerApp extends JFrame { this.setContentPane(mainPanel); - this.pack(); this.initLoggingHandler(); - this.setVisible(true); } /** @@ -77,7 +89,7 @@ public class InitializerApp extends JFrame { textEnterPanel.setBorder(BorderFactory.createLoweredBevelBorder()); textEnterPanel.add(new JLabel("Command:"), BorderLayout.WEST); JTextField commandField = new JTextField(); - commandField.addKeyListener(new CommandFieldKeyListener()); + commandField.addKeyListener(new CommandFieldKeyListener(this.executor)); textEnterPanel.add(commandField, BorderLayout.CENTER); // Top Label JLabel commandPanelLabel = new JLabel("Program output", SwingConstants.CENTER); diff --git a/src/main/java/nl/andrewlalis/util/Logging.java b/src/main/java/nl/andrewlalis/util/Logging.java index b9b7113..9161693 100644 --- a/src/main/java/nl/andrewlalis/util/Logging.java +++ b/src/main/java/nl/andrewlalis/util/Logging.java @@ -14,7 +14,7 @@ public class Logging { public static void setup(boolean verbose) throws IOException { Logger logger = Logger.getGlobal(); - outputFile = new FileHandler("log/latest.txt"); + outputFile = new FileHandler("log/latest.log"); formatter = new SimpleFormatter(); outputFile.setFormatter(formatter); From 2d40820025b3769d0a01cb19b5628f1a0d8acf32 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 26 Aug 2018 17:04:35 +0200 Subject: [PATCH 14/19] Added tons of stuff. --- src/main/java/nl/andrewlalis/Main.java | 29 ++- .../nl/andrewlalis/git_api/GithubManager.java | 168 +++++++++++------- .../nl/andrewlalis/model/StudentTeam.java | 15 +- .../java/nl/andrewlalis/model/TATeam.java | 17 +- .../andrewlalis/model/database/Database.java | 28 ++- .../ui/control/OutputTextHandler.java | 3 + .../ui/control/command/CommandExecutor.java | 16 +- .../command/executables/ArchiveRepos.java | 30 ++++ .../command/executables/GithubExecutable.java | 34 ++++ .../executables/ReadStudentsFileToDB.java | 36 ++++ .../listeners/CommandFieldKeyListener.java | 2 +- .../andrewlalis/ui/view/InitializerApp.java | 58 +++++- .../java/nl/andrewlalis/util/FileUtils.java | 4 +- src/main/resources/sql/insert/types.sql | 4 +- 14 files changed, 343 insertions(+), 101 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index e2aee76..681ac2d 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -6,6 +6,8 @@ import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.Executable; +import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; +import nl.andrewlalis.ui.control.command.executables.ReadStudentsFileToDB; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; @@ -38,18 +40,26 @@ public class Main { logger.severe("Unable to save log to file."); } + // Command executor which will be used by all actions the user can do. CommandExecutor executor = new CommandExecutor(); + + // Initialize User Interface. + InitializerApp app = new InitializerApp(executor); + app.begin(); + + Database db = new Database("database/initializer.sqlite"); + db.initialize(); + executor.registerCommand("test", args1 -> { System.out.println("TESTING"); return true; }); + executor.registerCommand("readstudents", new ReadStudentsFileToDB(db)); + executor.registerCommand("archiveall", new ArchiveRepos()); - // Initialize User Interface. - InitializerApp app = new InitializerApp(executor); + logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized."); - app.begin(); - logger.info("GithubManager for Github Repositories in Educational Organizations."); // Get studentTeams from CSV file. // List studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize"))); @@ -68,17 +78,6 @@ public class Main { } catch (Exception e) { e.printStackTrace(); } - - // Initialize database. -// Database db = new Database("database/initializer.sqlite"); -// db.initialize(); -// for (StudentTeam team : studentTeams) { -// for (Student student : team.getStudents()) { -// db.storeStudent(student); -// } -// } - - } diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index d7265a2..88ed56c 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import jdk.nashorn.internal.ir.annotations.Ignore; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.TATeam; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.entity.StringEntity; @@ -26,18 +27,6 @@ public class GithubManager { * The assignments repository where students will get assignments from. */ private GHRepository assignmentsRepo; - private String assignmentsRepoName; - - /** - * The name of the team which contains all teaching assistants. - */ - private String teachingAssistantsTeamName; - - /** - * The prefix used to prepend the names of student repositories. - * Should ideally contain the current school year. - */ - private String studentRepoPrefix; /** * Github object for API interactions. @@ -54,12 +43,8 @@ public class GithubManager { logger.setParent(Logger.getGlobal()); } - public GithubManager(String organizationName, String accessToken, String assignmentsRepo, String teachingAssistantsTeamName, String studentRepoPrefix) { - this.assignmentsRepoName = assignmentsRepo; - this.teachingAssistantsTeamName = teachingAssistantsTeamName; - this.studentRepoPrefix = studentRepoPrefix; + public GithubManager(String organizationName, String accessToken) { this.accessToken = accessToken; - try { this.github = GitHub.connectUsingOAuth(accessToken); this.organization = this.github.getOrganization(organizationName); @@ -80,57 +65,59 @@ public class GithubManager { * - adds students to repository * - adds all students to assignments repository. * @param studentTeams The list of student studentTeams. + * @param teamAll The team of all teaching assistants. + * @param assignmentsRepoName The name of the assignments repo. * @throws Exception If an error occurs while initializing the github repositories. */ - public void initializeGithubRepos(List studentTeams) throws Exception { - GHTeam teamAll = this.organization.getTeamByName(this.teachingAssistantsTeamName); - - this.setupAssignmentsRepo(teamAll); + public void initializeGithubRepos(List studentTeams, TATeam teamAll, String assignmentsRepoName) throws Exception { + this.setupAssignmentsRepo(assignmentsRepoName, "fuck the police", teamAll); StudentTeam t = new StudentTeam(); Student s = new Student(3050831, "Andrew Lalis", "andrewlalisofficial@gmail.com", "andrewlalis", null); t.addMember(s); t.setId(42); - this.setupStudentTeam(t, teamAll); + this.setupStudentTeam(t, teamAll, "advoop_2018"); + // TODO: Finish this method. } /** * Sets up the organization's assignments repository, and grants permissions to all teaching assistants. + * @param assignmentsRepoName The name of the assignments repository. + * @param description The description of the repository. * @param allTeachingAssistants A team consisting of all teaching assistants. * @throws IOException If an HTTP request failed. */ - @SuppressWarnings("deprecation") - private void setupAssignmentsRepo(GHTeam allTeachingAssistants) throws IOException { + private void setupAssignmentsRepo(String assignmentsRepoName, String description, TATeam allTeachingAssistants) throws IOException { // Check if the repository already exists. - GHRepository existingRepo = this.organization.getRepository(this.assignmentsRepoName); + GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); if (existingRepo != null) { existingRepo.delete(); logger.fine("Deleted pre-existing assignments repository."); } // Create the repository. - GHCreateRepositoryBuilder builder = this.organization.createRepository(this.assignmentsRepoName); + GHCreateRepositoryBuilder builder = this.organization.createRepository(assignmentsRepoName); builder.description("Assignments repository for Advanced Object Oriented Programming"); builder.wiki(false); builder.issues(true); builder.private_(false); // TODO: Make this true for production. - builder.team(allTeachingAssistants); + builder.team(allTeachingAssistants.getGithubTeam()); builder.gitignoreTemplate("Java"); this.assignmentsRepo = builder.create(); logger.info("Created assignments repository."); - // Protect the master branch. - GHBranchProtectionBuilder protectionBuilder = this.assignmentsRepo.getBranch("master").enableProtection(); - protectionBuilder.includeAdmins(false); - protectionBuilder.restrictPushAccess(); - protectionBuilder.teamPushAccess(allTeachingAssistants); - protectionBuilder.addRequiredChecks("ci/circleci"); - protectionBuilder.enable(); - logger.fine("Protected master branch of assignments repository."); + this.assignmentsRepo = this.createRepository(assignmentsRepoName, allTeachingAssistants, description, false, true, false); + + if (this.assignmentsRepo == null) { + logger.severe("Could not create assignments repository."); + return; + } + + this.protectMasterBranch(this.assignmentsRepo, allTeachingAssistants); // Grant all teaching assistants write access. - allTeachingAssistants.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); + allTeachingAssistants.getGithubTeam().add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); logger.fine("Gave admin rights to all teaching assistants in team: " + allTeachingAssistants.getName()); } @@ -139,44 +126,27 @@ public class GithubManager { * repository as well. * @param team The student team to set up. * @param taTeam The team of teaching assistants that is responsible for these students. + * @param prefix The prefix to append to the front of the repo name. * @throws IOException If an HTTP request fails. */ - @SuppressWarnings("deprecation") - private void setupStudentTeam(StudentTeam team, GHTeam taTeam) throws IOException { - String teamRepoName = team.generateUniqueName(this.studentRepoPrefix); - - Student[] students = team.getStudents(); - StringBuilder description = new StringBuilder("Group "); - description.append(team.getId()).append(": "); - - for (Student s : students) { - description.append(s.getName()).append(' '); + private void setupStudentTeam(StudentTeam team, TATeam taTeam, String prefix) throws IOException { + // First check that the assignments repo exists, otherwise no invitations can be sent. + if (this.assignmentsRepo == null) { + logger.warning("Assignments repository must be created before student repositories."); + return; } - GHCreateRepositoryBuilder builder = this.organization.createRepository(teamRepoName); - builder.team(taTeam); - builder.wiki(false); - builder.issues(true); - builder.description(description.toString()); - builder.gitignoreTemplate("Java"); - builder.private_(false); // TODO: Change this to true for production - GHRepository repo = builder.create(); - logger.info("Created repository: " + repo.getName()); + GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam, team.generateRepoDescription(), false, true, false); - // Protect the master branch. - GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection(); - protectionBuilder.includeAdmins(false); - protectionBuilder.teamPushAccess(taTeam); - protectionBuilder.addRequiredChecks("ci/circleci"); - protectionBuilder.enable(); - logger.fine("Protected master branch of repository: " + repo.getName()); + if (repo == null) { + logger.severe("Repository for student team " + team.getId() + " could not be created."); + return; + } - // Create development branch. - String sha1 = repo.getBranch(repo.getDefaultBranch()).getSHA1(); - repo.createRef("refs/heads/development", sha1); - logger.fine("Created development branch of repository: " + repo.getName()); + this.protectMasterBranch(repo, taTeam); + this.createDevelopmentBranch(repo); - taTeam.add(repo, GHOrganization.Permission.ADMIN); + taTeam.getGithubTeam().add(repo, GHOrganization.Permission.ADMIN); logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + repo.getName()); List users = new ArrayList<>(); @@ -234,4 +204,68 @@ public class GithubManager { logger.info("Archived repository: " + repo.getFullName()); } + /** + * Protects the master branch of a given repository, and gives admin rights to the given team. + * @param repo The repository to protect the master branch of. + * @param team The team which gets admin rights to the master branch. + */ + @SuppressWarnings("deprecation") + private void protectMasterBranch(GHRepository repo, TATeam team) { + try { + GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection(); + protectionBuilder.includeAdmins(false); + protectionBuilder.restrictPushAccess(); + protectionBuilder.teamPushAccess(team.getGithubTeam()); + protectionBuilder.addRequiredChecks("ci/circleci"); + protectionBuilder.enable(); + logger.fine("Protected master branch of repository: " + repo.getName()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Creates a development branch for the given repository. + * @param repo The repository to create a development branch for. + */ + private void createDevelopmentBranch(GHRepository repo) { + try { + String sha1 = repo.getBranch(repo.getDefaultBranch()).getSHA1(); + repo.createRef("refs/heads/development", sha1); + logger.fine("Created development branch of repository: " + repo.getName()); + } catch (IOException e) { + logger.severe("Could not create development branch for repository: " + repo.getName() + '\n' + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Creates a new github repository. + * @param name The name of the repository. + * @param taTeam The team to give admin rights. + * @param description The description of the repository. + * @param hasWiki Whether the repo has a wiki enabled. + * @param hasIssues Whether the repo has issues enabled. + * @param isPrivate Whether or not the repository is private. + * @return The repository that was created, or + */ + private GHRepository createRepository(String name, TATeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){ + try { + GHCreateRepositoryBuilder builder = this.organization.createRepository(name); + builder.team(taTeam.getGithubTeam()); + builder.wiki(hasWiki); + builder.issues(hasIssues); + builder.description(description); + builder.gitignoreTemplate("Java"); + builder.private_(isPrivate); // TODO: Change this to true for production + GHRepository repo = builder.create(); + logger.fine("Created repository: " + repo.getName()); + return repo; + } catch (IOException e) { + logger.severe("Could not create repository: " + name + '\n' + e.getMessage()); + e.printStackTrace(); + return null; + } + } + } diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index 4243aa2..6a4a9e5 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -52,9 +52,22 @@ public class StudentTeam extends Team{ public String generateUniqueName(String prefix) { StringBuilder sb = new StringBuilder(prefix); sb.append("_team_").append(this.id); - for (Student s : (Student[]) this.getMembers()) { + for (Student s : this.getStudents()) { sb.append('_').append(s.getNumber()); } return sb.toString(); } + + /** + * Generates a description for the repository, based on the students' names and group number. + * @return A description for the students' repository. + */ + public String generateRepoDescription() { + StringBuilder sb = new StringBuilder(); + sb.append("Group ").append(this.id).append(": "); + for (Student s : this.getStudents()) { + sb.append(s.getName()).append(' '); + } + return sb.toString(); + } } diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 0bbcdf3..2e252df 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -1,8 +1,7 @@ package nl.andrewlalis.model; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHTeam; import java.util.ArrayList; import java.util.List; @@ -25,6 +24,11 @@ public class TATeam { */ private int id; + /** + * The Github team associated with this team. + */ + private GHTeam githubTeam; + /** * Constructs a team without any teaching assistant members. * @param name The name of the team. @@ -58,4 +62,11 @@ public class TATeam { return this.name; } + public GHTeam getGithubTeam() { + return this.githubTeam; + } + + public void setGithubTeam(GHTeam team) { + this.githubTeam = team; + } } diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index 311dde3..d8878c3 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -1,9 +1,6 @@ package nl.andrewlalis.model.database; -import nl.andrewlalis.model.Person; -import nl.andrewlalis.model.Student; -import nl.andrewlalis.model.TeachingAssistant; -import nl.andrewlalis.model.Team; +import nl.andrewlalis.model.*; import java.sql.Connection; import java.sql.DriverManager; @@ -24,8 +21,8 @@ public class Database { private static final int TEAM_TYPE_TA = 1; private static final int TEAM_TYPE_TA_ALL = 2; - private static final int TEAM_NONE = 0; - private static final int TEAM_TA_ALL = 1; + private static final int TEAM_NONE = 1000000; + private static final int TEAM_TA_ALL = 1000001; private static final int ERROR_TYPE_TEAM = 0; private static final int ERROR_TYPE_PERSON = 1; @@ -157,6 +154,25 @@ public class Database { } } + /** + * Stores a list of student teams in the database. + * @param teams The list of teams to store. + * @return True if successful, or false if an error occurred. + */ + public boolean storeStudentTeams(List teams) { + for (StudentTeam team : teams) { + if (!this.storeTeam(team, TEAM_TYPE_STUDENT)) { + return false; + } + for (Student student : team.getStudents()) { + if (!this.storeStudent(student, team.getId())) { + return false; + } + } + } + return true; + } + /** * Stores a student without a team. * @param student The student to store. diff --git a/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java b/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java index a4d6710..875c148 100644 --- a/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java +++ b/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java @@ -8,6 +8,9 @@ import java.util.Date; import java.util.logging.Handler; import java.util.logging.LogRecord; +/** + * A custom handler for printing log messages to the user interface text output pane. + */ public class OutputTextHandler extends Handler { /** diff --git a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java index 222bfb3..dd5d84a 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java @@ -1,5 +1,6 @@ package nl.andrewlalis.ui.control.command; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; @@ -33,6 +34,7 @@ public class CommandExecutor { */ public void registerCommand(String commandName, Executable executable) { this.commands.put(commandName, executable); + logger.fine("Registered command: " + commandName); } /** @@ -40,7 +42,7 @@ public class CommandExecutor { * @param commandString The String command and any arguments that go with it. */ public void executeString(String commandString) { - String[] words = commandString.trim().toLowerCase().split(" "); + String[] words = commandString.trim().split(" "); if (words.length < 1) { logger.warning("No command supplied."); return; @@ -50,9 +52,19 @@ public class CommandExecutor { if (words.length > 1) { System.arraycopy(words, 1, args, 0, words.length - 1); } + this.executeCommand(commandName, args); + } + + /** + * Executes a command with the given name, and given arguments. + * @param commandName The name of the command. A command must be registered using registerCommand before it can be + * called here. + * @param args The list of arguments to provide to the command as needed by the executable that was registered. + */ + public void executeCommand(String commandName, String[] args) { if (this.commands.containsKey(commandName)) { + logger.info(commandName + ' ' + Arrays.toString(args)); this.commands.get(commandName).execute(args); - logger.info(commandString); } else { logger.warning(commandName + " is not a valid command."); } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java new file mode 100644 index 0000000..3f7368b --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java @@ -0,0 +1,30 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; + +import java.io.IOException; + +/** + * Represents the action archive all repositories with a certain substring in their name. + * It takes the following arguments: + * + * 1. Organization name + * 2. Access Token + * 3. Repo substring to archive by + */ +public class ArchiveRepos extends GithubExecutable { + + @Override + protected boolean executeWithManager(GithubManager manager, String[] args) { + if (args.length < 1) { + return false; + } + try { + manager.archiveAllRepositories(args[0]); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java new file mode 100644 index 0000000..e7362bd --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java @@ -0,0 +1,34 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.control.command.Executable; + +/** + * Represents an executable which interacts with github, and therefore needs access to a Github + * manager to execute. + * + * Requires two arguments: + * 1. The organization name. + * 2. The organization's access token. + */ +public abstract class GithubExecutable implements Executable { + + @Override + public boolean execute(String[] args) { + if (args.length < 2) { + return false; + } + String[] extraArgs = new String[args.length-2]; + System.arraycopy(args, 2, extraArgs, 0, args.length-2); + GithubManager manager = new GithubManager(args[0], args[1]); + return this.executeWithManager(manager, extraArgs); + } + + /** + * Executes a command and provides a github manager with which to perform operations. + * @param manager The GithubManager used to perform actions on the repositories. + * @param args Any additional arguments provided to the executable. + * @return True if successful, or false otherwise. + */ + protected abstract boolean executeWithManager(GithubManager manager, String[] args); +} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java new file mode 100644 index 0000000..2baf651 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java @@ -0,0 +1,36 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.database.Database; +import nl.andrewlalis.ui.control.command.Executable; +import nl.andrewlalis.util.FileUtils; + +import java.util.List; + +/** + * Execute this class to read students from a supplied filename and teamsize, and store their + * information in the database. + */ +public class ReadStudentsFileToDB implements Executable { + + /** + * The database used to store the students. + */ + private Database db; + + public ReadStudentsFileToDB(Database db) { + this.db = db; + } + + + @Override + public boolean execute(String[] args) { + if (args.length < 2) { + return false; + } + String filename = args[0]; + int teamSize = Integer.parseUnsignedInt(args[1]); + List teams = FileUtils.getStudentTeamsFromCSV(filename, teamSize); + return this.db.storeStudentTeams(teams); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java index a90f909..d68ac1e 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/CommandFieldKeyListener.java @@ -15,7 +15,7 @@ public class CommandFieldKeyListener implements KeyListener { /** * This is responsible for parsing and running entered commands. */ - CommandExecutor executor; + private CommandExecutor executor; public CommandFieldKeyListener(CommandExecutor executor) { this.executor = executor; diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 7a3b614..c9890a7 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -6,6 +6,7 @@ import nl.andrewlalis.ui.control.OutputTextHandler; import javax.swing.*; import java.awt.*; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -28,6 +29,13 @@ public class InitializerApp extends JFrame { */ private OutputTextPane outputTextPane; + private JTextField organizationField = new JTextField(); + private JTextField accessTokenField = new JTextField(); + private JTextField assignmentsRepoField = new JTextField(); + private JTextField teachingAssistantsField = new JTextField(); + private JTextField studentRepoField = new JTextField(); + private JTextField teamSizeField = new JTextField(); + /** * The executor responsible for performing meaningful actions. */ @@ -53,7 +61,9 @@ public class InitializerApp extends JFrame { */ private void initLoggingHandler() { Logger logger = Logger.getGlobal(); - logger.addHandler(new OutputTextHandler(this.outputTextPane)); + OutputTextHandler handler = new OutputTextHandler(this.outputTextPane); + handler.setLevel(Level.FINE); + logger.addHandler(handler); } /** @@ -68,12 +78,42 @@ public class InitializerApp extends JFrame { mainPanel.add(this.initCommandPanel(), BorderLayout.CENTER); mainPanel.add(this.initRepoPanel(), BorderLayout.WEST); + mainPanel.add(this.initGithubManagerPanel(), BorderLayout.EAST); this.setContentPane(mainPanel); this.initLoggingHandler(); } + /** + * @return A JPanel containing input for all fields needed to connect to github, plus some commonly used buttons + * which perform actions, as shortcuts for command actions. + */ + private JPanel initGithubManagerPanel() { + JPanel githubManagerPanel = new JPanel(new BorderLayout()); + + // Information input (org name, key, etc.) + JPanel infoInputPanel = new JPanel(); + infoInputPanel.setLayout(new BoxLayout(infoInputPanel, BoxLayout.PAGE_AXIS)); + + infoInputPanel.add(generateTextFieldPanel("Organization Name", this.organizationField)); + this.organizationField.setText("InitializerTesting"); + infoInputPanel.add(generateTextFieldPanel("Access Token", this.accessTokenField)); + this.accessTokenField.setText("d3699963f23cee85fe44c42f66057acc98c9ec7a"); + infoInputPanel.add(generateTextFieldPanel("Assignments Repo Name", this.assignmentsRepoField)); + this.assignmentsRepoField.setText("assignments_2018"); + infoInputPanel.add(generateTextFieldPanel("TA-All Team Name", this.teachingAssistantsField)); + this.teachingAssistantsField.setText("teaching-assistants"); + infoInputPanel.add(generateTextFieldPanel("Student Repo Prefix", this.studentRepoField)); + this.studentRepoField.setText("advoop_2018"); + infoInputPanel.add(generateTextFieldPanel("Team Size", this.teamSizeField)); + this.teamSizeField.setText("2"); + + githubManagerPanel.add(infoInputPanel, BorderLayout.NORTH); + + return githubManagerPanel; + } + /** * @return A JPanel containing the command prompt field and output text pane. */ @@ -83,7 +123,7 @@ public class InitializerApp extends JFrame { this.outputTextPane = new OutputTextPane(); JScrollPane scrollPane = new JScrollPane(this.outputTextPane); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // Text enter field and label. JPanel textEnterPanel = new JPanel(new BorderLayout()); textEnterPanel.setBorder(BorderFactory.createLoweredBevelBorder()); @@ -111,4 +151,18 @@ public class InitializerApp extends JFrame { return repoPanel; } + /** + * Generates a text input field panel. + * @param labelText The text for the label above the panel. + * @param textField A reference to the text field that is used in the panel. + * @return A JPanel containing the label and text field. + */ + private JPanel generateTextFieldPanel(String labelText, JTextField textField) { + JPanel newPanel = new JPanel(new BorderLayout()); + newPanel.add(new JLabel(labelText), BorderLayout.NORTH); + newPanel.add(textField); + newPanel.setBorder(BorderFactory.createEmptyBorder(5, 2, 5, 2)); + return newPanel; + } + } diff --git a/src/main/java/nl/andrewlalis/util/FileUtils.java b/src/main/java/nl/andrewlalis/util/FileUtils.java index f5cf3db..972ffb1 100644 --- a/src/main/java/nl/andrewlalis/util/FileUtils.java +++ b/src/main/java/nl/andrewlalis/util/FileUtils.java @@ -47,13 +47,13 @@ public class FileUtils { * @return A list of student teams. */ public static List getStudentTeamsFromCSV(String filename, int teamSize) { - List studentTeams = null; + List studentTeams; try { studentTeams = TeamGenerator.generateFromCSV(filename, teamSize); logger.fine("Teams created:\n" + studentTeams); return studentTeams; } catch (IOException | ArrayIndexOutOfBoundsException e) { - logger.severe("Unable to generate studentTeams from CSV file, exiting."); + logger.severe("Unable to generate studentTeams from CSV file, exiting. " + e.getMessage()); System.exit(1); return null; } diff --git a/src/main/resources/sql/insert/types.sql b/src/main/resources/sql/insert/types.sql index 45ab89d..3940146 100644 --- a/src/main/resources/sql/insert/types.sql +++ b/src/main/resources/sql/insert/types.sql @@ -10,8 +10,8 @@ VALUES (0, 'student_team'), (3, 'none'); INSERT INTO teams (id, team_type_id) -VALUES (0, 3), -- None team for all students or TA's without a team. - (1, 2); -- Team for all teaching assistants. +VALUES (1000000, 3), -- None team for all students or TA's without a team. + (1000001, 2); -- Team for all teaching assistants. INSERT INTO error_types (id, name) VALUES (0, 'team_error'), From cd0aa85dbaa225e76985e0aba2fbb65736489fa3 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 26 Aug 2018 17:09:59 +0200 Subject: [PATCH 15/19] Removed token. --- src/main/java/nl/andrewlalis/git_api/GithubManager.java | 1 - src/main/java/nl/andrewlalis/ui/view/InitializerApp.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 88ed56c..6d543bf 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -2,7 +2,6 @@ package nl.andrewlalis.git_api; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import jdk.nashorn.internal.ir.annotations.Ignore; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.model.TATeam; diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index c9890a7..e02ca8f 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -99,7 +99,7 @@ public class InitializerApp extends JFrame { infoInputPanel.add(generateTextFieldPanel("Organization Name", this.organizationField)); this.organizationField.setText("InitializerTesting"); infoInputPanel.add(generateTextFieldPanel("Access Token", this.accessTokenField)); - this.accessTokenField.setText("d3699963f23cee85fe44c42f66057acc98c9ec7a"); + this.accessTokenField.setText("haha get your own"); infoInputPanel.add(generateTextFieldPanel("Assignments Repo Name", this.assignmentsRepoField)); this.assignmentsRepoField.setText("assignments_2018"); infoInputPanel.add(generateTextFieldPanel("TA-All Team Name", this.teachingAssistantsField)); From 346419d5b6bf936d26ba331ec32bae897e283b8a Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 27 Aug 2018 17:53:31 +0200 Subject: [PATCH 16/19] Added stuff --- .../command/executables/ArchiveRepos.java | 4 +- .../executables/ReadStudentsFileToDB.java | 4 ++ .../control/listeners/ArchiveAllListener.java | 30 ++++++++++++ .../control/listeners/ExecutableListener.java | 19 ++++++++ .../andrewlalis/ui/view/InitializerApp.java | 46 ++++++++++++++++++- 5 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java index 3f7368b..5e43269 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java @@ -8,9 +8,7 @@ import java.io.IOException; * Represents the action archive all repositories with a certain substring in their name. * It takes the following arguments: * - * 1. Organization name - * 2. Access Token - * 3. Repo substring to archive by + * 1. Repo substring to archive by */ public class ArchiveRepos extends GithubExecutable { diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java index 2baf651..2d3ddc3 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java @@ -10,6 +10,10 @@ import java.util.List; /** * Execute this class to read students from a supplied filename and teamsize, and store their * information in the database. + * Requires the following arguments: + * + * 1. filename + * 2. teamsize */ public class ReadStudentsFileToDB implements Executable { diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java new file mode 100644 index 0000000..cbdca70 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java @@ -0,0 +1,30 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.view.InitializerApp; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ArchiveAllListener extends ExecutableListener { + + InitializerApp app; + + public ArchiveAllListener(CommandExecutor executor, InitializerApp app) { + super(executor); + this.app = app; + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + String response = JOptionPane.showInputDialog(null, "Enter a substring to archive repositories by.", "Enter a substring", JOptionPane.QUESTION_MESSAGE); + if (response != null) { + this.executor.executeCommand("archiveall", new String[]{ + this.app.getOrganizationName(), + this.app.getAccessToken(), + response + }); + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java new file mode 100644 index 0000000..545bfc5 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java @@ -0,0 +1,19 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.control.command.Executable; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * An action listener which is pre-set to execute an executable once an action is performed. + */ +public abstract class ExecutableListener implements ActionListener { + + protected CommandExecutor executor; + + public ExecutableListener(CommandExecutor executor) { + this.executor = executor; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index e02ca8f..109be5b 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -1,8 +1,10 @@ package nl.andrewlalis.ui.view; -import nl.andrewlalis.ui.control.command.CommandExecutor; -import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener; import nl.andrewlalis.ui.control.OutputTextHandler; +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; +import nl.andrewlalis.ui.control.listeners.ArchiveAllListener; +import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener; import javax.swing.*; import java.awt.*; @@ -111,6 +113,30 @@ public class InitializerApp extends JFrame { githubManagerPanel.add(infoInputPanel, BorderLayout.NORTH); + // Common actions panel. + JPanel commonActionsPanel = new JPanel(); + commonActionsPanel.setLayout(new BoxLayout(commonActionsPanel, BoxLayout.PAGE_AXIS)); + + JButton archiveAllButton = new JButton("Archive All"); + archiveAllButton.addActionListener(new ArchiveAllListener(this.executor, this)); + commonActionsPanel.add(archiveAllButton); + + JButton generateStudentTeamsButton = new JButton("Read teams from file"); + generateStudentTeamsButton.addActionListener(actionEvent -> { + JFileChooser chooser = new JFileChooser(); + int returnValue = chooser.showOpenDialog(null); + if (returnValue == JFileChooser.APPROVE_OPTION) { + this.executor.executeCommand("readstudents", new String[]{ + chooser.getSelectedFile().getName(), + teamSizeField.getText() + }); + } + + }); + commonActionsPanel.add(generateStudentTeamsButton); + + githubManagerPanel.add(commonActionsPanel, BorderLayout.CENTER); + return githubManagerPanel; } @@ -165,4 +191,20 @@ public class InitializerApp extends JFrame { return newPanel; } + /** + * Gets the organization name entered in the relevant field. + * @return The organization name the user has entered. + */ + public String getOrganizationName() { + return this.organizationField.getText().trim(); + } + + /** + * Gets the oauth access token entered in the relevant field. + * @return The access token the user has entered. + */ + public String getAccessToken() { + return this.accessTokenField.getText().trim(); + } + } From 8c8ddb60cf1e47091603f1e2bb5fdf5027e6cb83 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 28 Aug 2018 08:13:21 +0200 Subject: [PATCH 17/19] Added read students listener, improved repository descriptions for student teams. --- .../nl/andrewlalis/model/StudentTeam.java | 7 ++-- .../control/listeners/ArchiveAllListener.java | 11 +++---- .../control/listeners/ExecutableListener.java | 16 +++++++-- .../listeners/ReadStudentsFileListener.java | 33 +++++++++++++++++++ .../andrewlalis/ui/view/InitializerApp.java | 15 ++------- 5 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index 6a4a9e5..8967d27 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -65,8 +65,11 @@ public class StudentTeam extends Team{ public String generateRepoDescription() { StringBuilder sb = new StringBuilder(); sb.append("Group ").append(this.id).append(": "); - for (Student s : this.getStudents()) { - sb.append(s.getName()).append(' '); + for (int i = 0; i < this.memberCount(); i++) { + sb.append(this.getStudents()[i].getName()); + if (i != this.memberCount()-1) { + sb.append(", "); + } } return sb.toString(); } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java index cbdca70..6d3cbf2 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java @@ -5,20 +5,19 @@ import nl.andrewlalis.ui.view.InitializerApp; import javax.swing.*; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +/** + * Listens for when the user performs an action with the intent to archive all repositories. + */ public class ArchiveAllListener extends ExecutableListener { - InitializerApp app; - public ArchiveAllListener(CommandExecutor executor, InitializerApp app) { - super(executor); - this.app = app; + super(executor, app); } @Override public void actionPerformed(ActionEvent actionEvent) { - String response = JOptionPane.showInputDialog(null, "Enter a substring to archive repositories by.", "Enter a substring", JOptionPane.QUESTION_MESSAGE); + String response = JOptionPane.showInputDialog(this.app, "Enter a substring to archive repositories by.", "Enter a substring", JOptionPane.QUESTION_MESSAGE); if (response != null) { this.executor.executeCommand("archiveall", new String[]{ this.app.getOrganizationName(), diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java index 545bfc5..33401c0 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ExecutableListener.java @@ -1,19 +1,29 @@ package nl.andrewlalis.ui.control.listeners; import nl.andrewlalis.ui.control.command.CommandExecutor; -import nl.andrewlalis.ui.control.command.Executable; +import nl.andrewlalis.ui.view.InitializerApp; -import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * An action listener which is pre-set to execute an executable once an action is performed. + * Since these are used for the user interface, an instance of the application is passed, for the purpose of providing + * a parent component for many popups, and to have access to input fields. */ public abstract class ExecutableListener implements ActionListener { + /** + * The executor, with some registered commands that will be executed by listeners which extend this one. + */ protected CommandExecutor executor; - public ExecutableListener(CommandExecutor executor) { + /** + * An instance of the UI application. + */ + protected InitializerApp app; + + public ExecutableListener(CommandExecutor executor, InitializerApp app) { this.executor = executor; + this.app = app; } } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java new file mode 100644 index 0000000..788c431 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java @@ -0,0 +1,33 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.view.InitializerApp; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +/** + * Listens for when the user performs an action to read all students from a file, and output the contents to a database. + */ +public class ReadStudentsFileListener extends ExecutableListener { + + public ReadStudentsFileListener(CommandExecutor executor, InitializerApp app) { + super(executor, app); + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + JFileChooser chooser = new JFileChooser(); + int fileResponse = chooser.showOpenDialog(this.app); + + if (fileResponse == JFileChooser.APPROVE_OPTION) { + String teamSizeString = JOptionPane.showInputDialog(this.app, "Enter the student team size.", "Team Size", JOptionPane.QUESTION_MESSAGE); + if (teamSizeString != null) { + this.executor.executeCommand("readstudents", new String[]{ + chooser.getSelectedFile().getName(), + teamSizeString + }); + } + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 109be5b..78fe78f 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -5,6 +5,7 @@ import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; import nl.andrewlalis.ui.control.listeners.ArchiveAllListener; import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener; +import nl.andrewlalis.ui.control.listeners.ReadStudentsFileListener; import javax.swing.*; import java.awt.*; @@ -108,8 +109,6 @@ public class InitializerApp extends JFrame { this.teachingAssistantsField.setText("teaching-assistants"); infoInputPanel.add(generateTextFieldPanel("Student Repo Prefix", this.studentRepoField)); this.studentRepoField.setText("advoop_2018"); - infoInputPanel.add(generateTextFieldPanel("Team Size", this.teamSizeField)); - this.teamSizeField.setText("2"); githubManagerPanel.add(infoInputPanel, BorderLayout.NORTH); @@ -122,17 +121,7 @@ public class InitializerApp extends JFrame { commonActionsPanel.add(archiveAllButton); JButton generateStudentTeamsButton = new JButton("Read teams from file"); - generateStudentTeamsButton.addActionListener(actionEvent -> { - JFileChooser chooser = new JFileChooser(); - int returnValue = chooser.showOpenDialog(null); - if (returnValue == JFileChooser.APPROVE_OPTION) { - this.executor.executeCommand("readstudents", new String[]{ - chooser.getSelectedFile().getName(), - teamSizeField.getText() - }); - } - - }); + generateStudentTeamsButton.addActionListener(new ReadStudentsFileListener(this.executor, this)); commonActionsPanel.add(generateStudentTeamsButton); githubManagerPanel.add(commonActionsPanel, BorderLayout.CENTER); From 0233221c8ddc576324a109dc7ee7dd5da0811c2b Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 28 Aug 2018 08:34:07 +0200 Subject: [PATCH 18/19] Adding retrieval methods. --- .../nl/andrewlalis/git_api/GithubManager.java | 5 ++-- .../andrewlalis/model/database/Database.java | 30 +++++++++++++++---- .../executables/GenerateAssignmentsRepo.java | 16 ++++++++++ .../executables/GenerateStudentRepos.java | 12 ++++++++ .../andrewlalis/ui/view/InitializerApp.java | 1 - 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 6d543bf..d188e53 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -87,7 +87,7 @@ public class GithubManager { * @param allTeachingAssistants A team consisting of all teaching assistants. * @throws IOException If an HTTP request failed. */ - private void setupAssignmentsRepo(String assignmentsRepoName, String description, TATeam allTeachingAssistants) throws IOException { + public void setupAssignmentsRepo(String assignmentsRepoName, String description, TATeam allTeachingAssistants) throws IOException { // Check if the repository already exists. GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); if (existingRepo != null) { @@ -128,7 +128,7 @@ public class GithubManager { * @param prefix The prefix to append to the front of the repo name. * @throws IOException If an HTTP request fails. */ - private void setupStudentTeam(StudentTeam team, TATeam taTeam, String prefix) throws IOException { + public void setupStudentTeam(StudentTeam team, TATeam taTeam, String prefix) throws IOException { // First check that the assignments repo exists, otherwise no invitations can be sent. if (this.assignmentsRepo == null) { logger.warning("Assignments repository must be created before student repositories."); @@ -219,6 +219,7 @@ public class GithubManager { protectionBuilder.enable(); logger.fine("Protected master branch of repository: " + repo.getName()); } catch (IOException e) { + logger.severe("Could not protect master branch of repository: " + repo.getName()); e.printStackTrace(); } } diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index d8878c3..180124e 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -2,10 +2,7 @@ package nl.andrewlalis.model.database; import nl.andrewlalis.model.*; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.sql.*; import java.util.List; import java.util.logging.Logger; @@ -60,7 +57,7 @@ public class Database { try { statement.execute(); } catch (SQLException e) { - logger.severe("SQLException while executing prepared statement: " + statement.toString() + ". Code: " + e.getErrorCode()); + logger.severe("SQLException while executing prepared statement:\n" + statement.toString() + "\nCode: " + e.getErrorCode()); return false; } } @@ -70,7 +67,7 @@ public class Database { try { statement.execute(); } catch (SQLException e) { - logger.severe("SQLException while inserting into table: " + statement.toString() + ". Code: " + e.getErrorCode()); + logger.severe("SQLException while inserting into table:\n" + statement.toString() + "\nCode: " + e.getErrorCode()); return false; } } @@ -102,6 +99,25 @@ public class Database { } } + private Student retrieveStudent(int id) { + + } + + private Person retrievePerson(int id) { + try { + logger.finest("Retrieving person (id=" + id + ")."); + String sql = "SELECT * FROM persons WHERE id=?"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, id); + ResultSet result = stmt.executeQuery(); + Person p = new Person(id, result.getString("name"), result.getString("email_address"), result.getString("github_username")); + } catch (SQLException e) { + logger.severe("SQL Exception while retrieving Person.\n" + e.getMessage()); + e.printStackTrace(); + return false; + } + } + /** * Stores a teaching assistant without a team. * @param ta The teaching assistant to store. @@ -129,6 +145,7 @@ public class Database { stmt.execute(); return true; } catch (SQLException e) { + logger.severe("SQL Exception while inserting TeachingAssistant.\n" + e.getMessage()); e.printStackTrace(); return false; } @@ -202,6 +219,7 @@ public class Database { stmt.execute(); return true; } catch (SQLException e) { + logger.severe("SQL Exception while inserting Student into database.\n" + e.getMessage()); e.printStackTrace(); return false; } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java new file mode 100644 index 0000000..d09d887 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java @@ -0,0 +1,16 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; + +/** + * Generates the assignments repository, with the supplied github manager, as well as the following extra arguments: + */ +public class GenerateAssignmentsRepo extends GithubExecutable { + + @Override + protected boolean executeWithManager(GithubManager manager, String[] args) { + + manager.setupAssignmentsRepo(args[0], args[1], ); + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java new file mode 100644 index 0000000..41f7975 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java @@ -0,0 +1,12 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; + +public class GenerateStudentRepos extends GithubExecutable { + + @Override + protected boolean executeWithManager(GithubManager manager, String[] args) { + return false; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 78fe78f..e7384e5 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -37,7 +37,6 @@ public class InitializerApp extends JFrame { private JTextField assignmentsRepoField = new JTextField(); private JTextField teachingAssistantsField = new JTextField(); private JTextField studentRepoField = new JTextField(); - private JTextField teamSizeField = new JTextField(); /** * The executor responsible for performing meaningful actions. From 6b1ad847695ffe20af8e5c3665e1ce95b7ce2a7d Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 28 Aug 2018 15:04:04 +0200 Subject: [PATCH 19/19] Fixed duplicate assignments repo generation. --- src/main/java/nl/andrewlalis/Main.java | 6 +-- .../nl/andrewlalis/git_api/GithubManager.java | 41 +++++++--------- .../andrewlalis/model/database/Database.java | 47 ++++++++++++++++--- .../executables/GenerateAssignmentsRepo.java | 18 ++++++- .../GenerateAssignmentsRepoListener.java | 36 ++++++++++++++ .../andrewlalis/ui/view/InitializerApp.java | 7 ++- src/main/resources/sql/table_init.sql | 10 ++++ 7 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 681ac2d..b6e7202 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -7,6 +7,7 @@ import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.Executable; import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; +import nl.andrewlalis.ui.control.command.executables.GenerateAssignmentsRepo; import nl.andrewlalis.ui.control.command.executables.ReadStudentsFileToDB; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; @@ -50,12 +51,9 @@ public class Main { Database db = new Database("database/initializer.sqlite"); db.initialize(); - executor.registerCommand("test", args1 -> { - System.out.println("TESTING"); - return true; - }); executor.registerCommand("readstudents", new ReadStudentsFileToDB(db)); executor.registerCommand("archiveall", new ArchiveRepos()); + executor.registerCommand("generateassignments", new GenerateAssignmentsRepo()); logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized."); diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index d188e53..5de6ec7 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -48,6 +48,7 @@ public class GithubManager { this.github = GitHub.connectUsingOAuth(accessToken); this.organization = this.github.getOrganization(organizationName); } catch (IOException e) { + logger.severe("Unable to make a GithubManager with organization name: " + organizationName + " and access token: " + accessToken); e.printStackTrace(); } } @@ -69,14 +70,14 @@ public class GithubManager { * @throws Exception If an error occurs while initializing the github repositories. */ public void initializeGithubRepos(List studentTeams, TATeam teamAll, String assignmentsRepoName) throws Exception { - this.setupAssignmentsRepo(assignmentsRepoName, "fuck the police", teamAll); + this.setupAssignmentsRepo(assignmentsRepoName, "fuck the police", teamAll.getName()); StudentTeam t = new StudentTeam(); Student s = new Student(3050831, "Andrew Lalis", "andrewlalisofficial@gmail.com", "andrewlalis", null); t.addMember(s); t.setId(42); - this.setupStudentTeam(t, teamAll, "advoop_2018"); + this.setupStudentTeam(t, teamAll.getGithubTeam(), "advoop_2018"); // TODO: Finish this method. } @@ -84,10 +85,11 @@ public class GithubManager { * Sets up the organization's assignments repository, and grants permissions to all teaching assistants. * @param assignmentsRepoName The name of the assignments repository. * @param description The description of the repository. - * @param allTeachingAssistants A team consisting of all teaching assistants. + * @param allTeachingAssistants The name of the team consisting of all teaching assistants. * @throws IOException If an HTTP request failed. */ - public void setupAssignmentsRepo(String assignmentsRepoName, String description, TATeam allTeachingAssistants) throws IOException { + public void setupAssignmentsRepo(String assignmentsRepoName, String description, String allTeachingAssistants) throws IOException { + GHTeam team = this.organization.getTeamByName(allTeachingAssistants); // Check if the repository already exists. GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); if (existingRepo != null) { @@ -95,29 +97,18 @@ public class GithubManager { logger.fine("Deleted pre-existing assignments repository."); } - // Create the repository. - GHCreateRepositoryBuilder builder = this.organization.createRepository(assignmentsRepoName); - builder.description("Assignments repository for Advanced Object Oriented Programming"); - builder.wiki(false); - builder.issues(true); - builder.private_(false); // TODO: Make this true for production. - builder.team(allTeachingAssistants.getGithubTeam()); - builder.gitignoreTemplate("Java"); - this.assignmentsRepo = builder.create(); - logger.info("Created assignments repository."); - - this.assignmentsRepo = this.createRepository(assignmentsRepoName, allTeachingAssistants, description, false, true, false); + this.assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, false); if (this.assignmentsRepo == null) { logger.severe("Could not create assignments repository."); return; } - this.protectMasterBranch(this.assignmentsRepo, allTeachingAssistants); + this.protectMasterBranch(this.assignmentsRepo, team); // Grant all teaching assistants write access. - allTeachingAssistants.getGithubTeam().add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); - logger.fine("Gave admin rights to all teaching assistants in team: " + allTeachingAssistants.getName()); + team.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN); + logger.fine("Gave admin rights to all teaching assistants in team: " + team.getName()); } /** @@ -128,7 +119,7 @@ public class GithubManager { * @param prefix The prefix to append to the front of the repo name. * @throws IOException If an HTTP request fails. */ - public void setupStudentTeam(StudentTeam team, TATeam taTeam, String prefix) throws IOException { + public void setupStudentTeam(StudentTeam team, GHTeam taTeam, String prefix) throws IOException { // First check that the assignments repo exists, otherwise no invitations can be sent. if (this.assignmentsRepo == null) { logger.warning("Assignments repository must be created before student repositories."); @@ -145,7 +136,7 @@ public class GithubManager { this.protectMasterBranch(repo, taTeam); this.createDevelopmentBranch(repo); - taTeam.getGithubTeam().add(repo, GHOrganization.Permission.ADMIN); + taTeam.add(repo, GHOrganization.Permission.ADMIN); logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + repo.getName()); List users = new ArrayList<>(); @@ -209,12 +200,12 @@ public class GithubManager { * @param team The team which gets admin rights to the master branch. */ @SuppressWarnings("deprecation") - private void protectMasterBranch(GHRepository repo, TATeam team) { + private void protectMasterBranch(GHRepository repo, GHTeam team) { try { GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection(); protectionBuilder.includeAdmins(false); protectionBuilder.restrictPushAccess(); - protectionBuilder.teamPushAccess(team.getGithubTeam()); + protectionBuilder.teamPushAccess(team); protectionBuilder.addRequiredChecks("ci/circleci"); protectionBuilder.enable(); logger.fine("Protected master branch of repository: " + repo.getName()); @@ -249,10 +240,10 @@ public class GithubManager { * @param isPrivate Whether or not the repository is private. * @return The repository that was created, or */ - private GHRepository createRepository(String name, TATeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){ + private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){ try { GHCreateRepositoryBuilder builder = this.organization.createRepository(name); - builder.team(taTeam.getGithubTeam()); + builder.team(taTeam); builder.wiki(hasWiki); builder.issues(hasIssues); builder.description(description); diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index 180124e..14c8626 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -3,6 +3,7 @@ package nl.andrewlalis.model.database; import nl.andrewlalis.model.*; import java.sql.*; +import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; @@ -99,22 +100,46 @@ public class Database { } } - private Student retrieveStudent(int id) { - + /** + * Retrieves a list of preferred partners that each student has set. + * @param studentId The student id to search by. + * @return A list of student id's for all students that the given student wishes to be their partner. + */ + private List retrievePreferredPartners(int studentId) { + try { + logger.finest("Retrieving preferred partners of student: " + studentId); + String sql = "SELECT partner_id FROM student_preferred_partners WHERE student_id=?;"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, studentId); + ResultSet results = stmt.executeQuery(); + List partners = new ArrayList<>(); + while (results.next()) { + partners.add(results.getInt(1)); + } + return partners; + } catch (SQLException e) { + logger.severe("SQL Exception while retrieving preferred partners of student: " + studentId + '\n' + e.getMessage()); + e.printStackTrace(); + return new ArrayList<>(); + } } - private Person retrievePerson(int id) { + /** + * Retrieves a student by their id. + * @param id The id of the student (student number) + * @return The student corresponding to this number, or null if it could not be found. + */ + public Student retrieveStudent(int id) { try { - logger.finest("Retrieving person (id=" + id + ")."); String sql = "SELECT * FROM persons WHERE id=?"; PreparedStatement stmt = this.connection.prepareStatement(sql); stmt.setInt(1, id); ResultSet result = stmt.executeQuery(); - Person p = new Person(id, result.getString("name"), result.getString("email_address"), result.getString("github_username")); + return new Student(id, result.getString("name"), result.getString("email_address"), result.getString("github_username"), this.retrievePreferredPartners(id)); } catch (SQLException e) { - logger.severe("SQL Exception while retrieving Person.\n" + e.getMessage()); + logger.severe("SQL Exception while retrieving Student.\n" + e.getMessage()); e.printStackTrace(); - return false; + return null; } } @@ -217,6 +242,14 @@ public class Database { stmt.setInt(2, teamId); stmt.setInt(3, student.getPreferredPartners().size() > 0 ? 1 : 0); stmt.execute(); + // Storing partners. + String sqlPartner = "INSERT INTO student_preferred_partners (student_id, partner_id) VALUES (?, ?);"; + PreparedStatement stmtPartner = this.connection.prepareStatement(sqlPartner); + for (int partnerId : student.getPreferredPartners()) { + stmtPartner.setInt(1, student.getNumber()); + stmtPartner.setInt(2, partnerId); + stmtPartner.execute(); + } return true; } catch (SQLException e) { logger.severe("SQL Exception while inserting Student into database.\n" + e.getMessage()); diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java index d09d887..2ccb06b 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateAssignmentsRepo.java @@ -2,15 +2,29 @@ package nl.andrewlalis.ui.control.command.executables; import nl.andrewlalis.git_api.GithubManager; +import java.io.IOException; + /** * Generates the assignments repository, with the supplied github manager, as well as the following extra arguments: + * + * 1. The name of the repository. + * 2. Description of the repository. + * 3. Name of TA team containing all members. */ public class GenerateAssignmentsRepo extends GithubExecutable { @Override protected boolean executeWithManager(GithubManager manager, String[] args) { - - manager.setupAssignmentsRepo(args[0], args[1], ); + if (args.length < 3) { + return false; + } + try { + manager.setupAssignmentsRepo(args[0], args[1], args[2]); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } } } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java new file mode 100644 index 0000000..6143821 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java @@ -0,0 +1,36 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.view.InitializerApp; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +/** + * Listens for when the user does an action to generate the assignments repository. + */ +public class GenerateAssignmentsRepoListener extends ExecutableListener { + + public GenerateAssignmentsRepoListener(CommandExecutor executor, InitializerApp app) { + super(executor, app); + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + String repoName = JOptionPane.showInputDialog(this.app, "Enter a name for the assignments repository.", "Repository Name", JOptionPane.QUESTION_MESSAGE); + if (repoName != null) { + String description = JOptionPane.showInputDialog(this.app, "Enter a description for the repository.", "Repository Description", JOptionPane.QUESTION_MESSAGE); + String teamName = JOptionPane.showInputDialog(this.app, "Enter the name of the TA team containing all teaching assistants.", "TA Team Name", JOptionPane.QUESTION_MESSAGE); + if (teamName != null) { + this.executor.executeCommand("generateassignments", new String[]{ + this.app.getOrganizationName(), + this.app.getAccessToken(), + repoName, + description, + teamName + }); + } + } + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index e7384e5..2ff6c2a 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -5,6 +5,7 @@ import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; import nl.andrewlalis.ui.control.listeners.ArchiveAllListener; import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener; +import nl.andrewlalis.ui.control.listeners.GenerateAssignmentsRepoListener; import nl.andrewlalis.ui.control.listeners.ReadStudentsFileListener; import javax.swing.*; @@ -25,7 +26,7 @@ public class InitializerApp extends JFrame { /** * A default size of the window on startup. */ - private static final Dimension SIZE = new Dimension(800, 600); + private static final Dimension SIZE = new Dimension(1000, 600); /** * The pane on which general purpose program output is written. @@ -123,6 +124,10 @@ public class InitializerApp extends JFrame { generateStudentTeamsButton.addActionListener(new ReadStudentsFileListener(this.executor, this)); commonActionsPanel.add(generateStudentTeamsButton); + JButton generateAssignmentsRepoButton = new JButton("Generate Assignments Repo"); + generateAssignmentsRepoButton.addActionListener(new GenerateAssignmentsRepoListener(this.executor, this)); + commonActionsPanel.add(generateAssignmentsRepoButton); + githubManagerPanel.add(commonActionsPanel, BorderLayout.CENTER); return githubManagerPanel; diff --git a/src/main/resources/sql/table_init.sql b/src/main/resources/sql/table_init.sql index 2604917..e5ffa1d 100644 --- a/src/main/resources/sql/table_init.sql +++ b/src/main/resources/sql/table_init.sql @@ -73,6 +73,16 @@ CREATE TABLE IF NOT EXISTS students ( ON UPDATE CASCADE ); +CREATE TABLE IF NOT EXISTS student_preferred_partners ( + student_id INTEGER PRIMARY KEY, + partner_id INTEGER NOT NULL, + FOREIGN KEY (student_id) + REFERENCES students(person_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + UNIQUE (student_id, partner_id) +); + CREATE TABLE IF NOT EXISTS teaching_assistants ( person_id INTEGER PRIMARY KEY, team_id INTEGER NOT NULL,