/**
* Yobi, Project Hosting SW
*
* Copyright 2013 NAVER Corp.
* http://yobi.io
*
* @Author Wansoon Park
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers;
import actions.AnonymousCheckAction;
import actors.PullRequestMergingActor;
import akka.actor.Props;
import com.avaje.ebean.Page;
import controllers.annotation.*;
import models.*;
import models.enumeration.Operation;
import models.enumeration.ResourceType;
import models.enumeration.RoleType;
import models.enumeration.State;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.node.ObjectNode;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.tmatesoft.svn.core.SVNException;
import play.api.mvc.Call;
import play.data.Form;
import play.db.ebean.Transactional;
import play.libs.Akka;
import play.libs.F.Function;
import play.libs.F.Promise;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.With;
import playRepository.*;
import utils.*;
import views.html.git.*;
import views.html.error.notfound;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@IsOnlyGitAvailable
public class PullRequestApp extends Controller {
@With(AnonymousCheckAction.class)
@IsCreatable(ResourceType.FORK)
public static Result newFork(String userName, String projectName, String forkOwner) {
String destination = findDestination(forkOwner);
Project project = Project.findByOwnerAndProjectName(userName, projectName);
List<OrganizationUser> orgUserList = OrganizationUser.findByAdmin(UserApp.currentUser().id);
List<Project> forkedProjects = Project.findByOwnerAndOriginalProject(destination, project);
Project forkProject = Project.copy(project, destination);
return ok(fork.render("fork", project, forkProject, forkedProjects, new Form<>(Project.class), orgUserList));
}
private static String findDestination(String forkOwner) {
Organization organization = Organization.findByName(forkOwner);
if (OrganizationUser.isAdmin(organization, UserApp.currentUser())) {
return forkOwner;
}
return UserApp.currentUser().loginId;
}
@With(AnonymousCheckAction.class)
@IsCreatable(ResourceType.FORK)
public static Result fork(String userName, String projectName) {
Form<Project> forkProjectForm = new Form<>(Project.class).bindFromRequest();
Project projectForm = forkProjectForm.get();
String destination = findDestination(projectForm.owner);
Project originalProject = Project.findByOwnerAndProjectName(userName, projectName);
if (Project.exists(destination, projectForm.name)) {
flash(Constants.WARNING, "project.name.duplicate");
forkProjectForm.reject("name");
return redirect(routes.PullRequestApp.newFork(originalProject.owner, originalProject.name, destination));
}
Project forkProject = Project.copy(originalProject, destination);
forkProject.name = projectForm.name;
forkProject.projectScope = projectForm.projectScope;
originalProject.addFork(forkProject);
return ok(clone.render("fork", forkProject));
}
@Transactional
@IsCreatable(ResourceType.FORK)
public static Result doClone(String userName, String projectName) {
Form<Project> form = new Form<>(Project.class).bindFromRequest();
Project projectForm = form.get();
String destination = findDestination(projectForm.owner);
String status = "status";
String failed = "failed";
String url = "url";
ObjectNode result = Json.newObject();
Project originalProject = Project.findByOwnerAndProjectName(userName, projectName);
if(originalProject == null) {
result.put(status, failed);
result.put(url, routes.Application.index().url());
return ok(result);
}
User currentUser = UserApp.currentUser();
if(!AccessControl.isProjectResourceCreatable(currentUser, originalProject, ResourceType.FORK)) {
result.put(status, failed);
result.put(url, routes.UserApp.loginForm().url());
return ok(result);
}
Project forkProject = Project.copy(originalProject, destination);
forkProject.name = projectForm.name;
forkProject.projectScope = projectForm.projectScope;
if (Organization.isNameExist(destination)) {
forkProject.organization = Organization.findByName(destination);
}
originalProject.addFork(forkProject);
try {
GitRepository.cloneLocalRepository(originalProject, forkProject);
Long projectId = Project.create(forkProject);
ProjectUser.assignRole(currentUser.id, projectId, RoleType.MANAGER);
result.put(status, "success");
result.put(url, routes.ProjectApp.project(forkProject.owner, forkProject.name).url());
return ok(result);
} catch (Exception e) {
play.Logger.error(MessageFormat.format("Failed to fork \"{0}\"", originalProject), e);
result.put(status, failed);
result.put(url, routes.PullRequestApp.pullRequests(originalProject.owner, originalProject.name).url());
return ok(result);
}
}
@With(AnonymousCheckAction.class)
@IsCreatable(ResourceType.FORK)
public static Result newPullRequestForm(String userName, String projectName) throws IOException, GitAPIException {
final Project project = Project.findByOwnerAndProjectName(userName, projectName);
ValidationResult validation = validateBeforePullRequest(project);
if(validation.hasError()) {
return validation.getResult();
}
final List<Project> projects = project.getAssociationProjects();
final Project fromProject = getSelectedProject(project, request().getQueryString("fromProjectId"), false);
final Project toProject = getSelectedProject(project, request().getQueryString("toProjectId"), true);
final List<GitBranch> fromBranches = new GitRepository(fromProject).getAllBranches();
final List<GitBranch> toBranches = new GitRepository(toProject).getAllBranches();
if(fromBranches.isEmpty()) {
return badRequest(ErrorViews.BadRequest.render("error.pullRerquest.empty.from.repository", fromProject, MenuType.PULL_REQUEST));
}
if(toBranches.isEmpty()) {
return badRequest(ErrorViews.BadRequest.render("error.pullRerquest.empty.to.repository", toProject));
}
final PullRequest pullRequest = PullRequest.createNewPullRequest(fromProject, toProject
, StringUtils.defaultIfBlank(request().getQueryString("fromBranch"), fromBranches.get(0).getName())
, StringUtils.defaultIfBlank(request().getQueryString("toBranch"), project.defaultBranch()));
return ok(create.render("title.newPullRequest", new Form<>(PullRequest.class).fill(pullRequest), project, projects, fromProject, toProject, fromBranches, toBranches, pullRequest));
}
private static Project getSelectedProject(Project project, String projectId, boolean isToProject) {
Project selectedProject = project;
if(isToProject && project.isForkedFromOrigin() && project.originalProject.menuSetting.code
&& project.originalProject.menuSetting.pullRequest) {
selectedProject = project.originalProject;
}
if(StringUtils.isNumeric(projectId)) {
selectedProject = Project.find.byId(Long.parseLong(projectId));
}
return selectedProject;
}
@With(AnonymousCheckAction.class)
@IsCreatable(ResourceType.FORK)
public static Result mergeResult(String userName, String projectName) throws IOException, GitAPIException {
final Project project = Project.findByOwnerAndProjectName(userName, projectName);
ValidationResult validation = validateBeforePullRequest(project);
if(validation.hasError()) {
return validation.getResult();
}
final Project fromProject = getSelectedProject(project, request().getQueryString("fromProjectId"), false);
final Project toProject = getSelectedProject(project, request().getQueryString("toProjectId"), true);
final List<GitBranch> fromBranches = new GitRepository(fromProject).getAllBranches();
final List<GitBranch> toBranches = new GitRepository(toProject).getAllBranches();
final PullRequest pullRequest = PullRequest.createNewPullRequest(fromProject, toProject
, StringUtils.defaultIfBlank(request().getQueryString("fromBranch"), fromBranches.get(0).getName())
, StringUtils.defaultIfBlank(request().getQueryString("toBranch"), toBranches.get(0).getName()));
PullRequestMergeResult mergeResult = pullRequest.getPullRequestMergeResult();
response().setHeader("Cache-Control", "no-cache, no-store");
return ok(partial_merge_result.render(project, pullRequest, mergeResult));
}
@Transactional
@With(AnonymousCheckAction.class)
@IsCreatable(ResourceType.FORK)
public static Result newPullRequest(String userName, String projectName) throws IOException, GitAPIException {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
ValidationResult validation = validateBeforePullRequest(project);
if(validation.hasError()) {
return validation.getResult();
}
Form<PullRequest> form = new Form<>(PullRequest.class).bindFromRequest();
validateForm(form);
if(form.hasErrors()) {
List<GitBranch> fromBranches = new GitRepository(project).getAllBranches();
List<GitBranch> toBranches = new GitRepository(project.originalProject).getAllBranches();
return ok(create.render("title.newPullRequest", new Form<>(PullRequest.class), project, null, null, null, fromBranches, toBranches, null));
}
PullRequest pullRequest = form.get();
if (pullRequest.body == null) {
return status(REQUEST_ENTITY_TOO_LARGE,
ErrorViews.RequestTextEntityTooLarge.render());
}
pullRequest.created = JodaDateUtil.now();
pullRequest.contributor = UserApp.currentUser();
pullRequest.toProject = Project.find.byId(pullRequest.toProjectId);
pullRequest.fromProject = Project.find.byId(pullRequest.fromProjectId);
pullRequest.isMerging = false;
pullRequest.isConflict = false;
PullRequest sentRequest = PullRequest.findDuplicatedPullRequest(pullRequest);
if(sentRequest != null) {
return redirect(routes.PullRequestApp.pullRequest(pullRequest.toProject.owner, pullRequest.toProject.name, sentRequest.number));
}
pullRequest.save();
PushedBranch.removeByPullRequestFrom(pullRequest);
AbstractPostingApp.attachUploadFilesToPost(pullRequest.asResource());
Call pullRequestCall = routes.PullRequestApp.pullRequest(pullRequest.toProject.owner, pullRequest.toProject.name, pullRequest.number);
NotificationEvent notiEvent = NotificationEvent.afterNewPullRequest(pullRequest);
PullRequestEvent.addFromNotificationEvent(notiEvent, pullRequest);
PullRequestEventMessage message = new PullRequestEventMessage(
UserApp.currentUser(), request(), pullRequest);
Akka.system().actorOf(new Props(PullRequestMergingActor.class)).tell(message, null);
return redirect(pullRequestCall);
}
private static void validateForm(Form<PullRequest> form) {
Map<String, String> data = form.data();
ValidationUtils.rejectIfEmpty(flash(), data.get("fromBranch"), "pullRequest.fromBranch.required");
ValidationUtils.rejectIfEmpty(flash(), data.get("toBranch"), "pullRequest.toBranch.required");
ValidationUtils.rejectIfEmpty(flash(), data.get("title"), "pullRequest.title.required");
}
@IsAllowed(Operation.READ)
public static Result pullRequests(String userName, String projectName) {
return pullRequests(userName, projectName, Category.OPEN);
}
@IsAllowed(Operation.READ)
public static Result closedPullRequests(String userName, String projectName) {
return pullRequests(userName, projectName, Category.CLOSED);
}
@IsAllowed(Operation.READ)
public static Result sentPullRequests(String userName, String projectName) {
return pullRequests(userName, projectName, Category.SENT);
}
private static Result pullRequests(String userName, String projectName, Category category) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
SearchCondition condition = Form.form(SearchCondition.class).bindFromRequest().get();
condition.setProject(project).setCategory(category);
Page<PullRequest> page = PullRequest.findPagingList(condition);
if (HttpUtil.isPJAXRequest(request())) {
response().setHeader("Cache-Control", "no-cache, no-store");
return ok(partial_search.render(project, page, condition, category.code));
} else {
return ok(list.render(project, page, condition, category.code));
}
}
@IsAllowed(value = Operation.READ, resourceType = ResourceType.PULL_REQUEST)
public static Result pullRequest(String userName, String projectName, long pullRequestNumber) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
boolean canDeleteBranch = false;
boolean canRestoreBranch = false;
if (pullRequest.isMerged()) {
canDeleteBranch = GitRepository.canDeleteFromBranch(pullRequest);
canRestoreBranch = GitRepository.canRestoreBranch(pullRequest);
}
UserApp.currentUser().visits(project);
return ok(view.render(project, pullRequest, canDeleteBranch, canRestoreBranch));
}
@IsAllowed(value = Operation.READ, resourceType = ResourceType.PULL_REQUEST)
public static Result pullRequestState(String userName, String projectName, long pullRequestNumber) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
boolean canDeleteBranch = false;
boolean canRestoreBranch = false;
if (pullRequest.isMerged()) {
canDeleteBranch = GitRepository.canDeleteFromBranch(pullRequest);
canRestoreBranch = GitRepository.canRestoreBranch(pullRequest);
}
if(HttpUtil.isRequestedWithXHR(request()) && !HttpUtil.isPJAXRequest(request())){
ObjectNode requestState = Json.newObject();
requestState.put("id", pullRequestNumber);
requestState.put("isOpen", pullRequest.isOpen());
requestState.put("isClosed", pullRequest.isClosed());
requestState.put("isMerged", pullRequest.isMerged());
requestState.put("isMerging", pullRequest.isMerging);
requestState.put("isConflict", pullRequest.isConflict);
requestState.put("canDeleteBranch", canDeleteBranch);
requestState.put("canRestoreBranch", canRestoreBranch);
requestState.put("html", partial_state.render(project, pullRequest, canDeleteBranch, canRestoreBranch).toString());
return ok(requestState);
}
return ok(partial_state.render(project, pullRequest, canDeleteBranch, canRestoreBranch));
}
@IsAllowed(value = Operation.READ, resourceType = ResourceType.PULL_REQUEST)
public static Result pullRequestChanges(String userName, String projectName,
long pullRequestNumber) {
return specificChange(userName, projectName, pullRequestNumber, null);
}
@IsAllowed(value = Operation.READ, resourceType = ResourceType.PULL_REQUEST)
public static Result specificChange(String userName, String projectName,
long pullRequestNumber, String commitId) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
return ok(viewChanges.render(project, pullRequest, commitId));
}
@Transactional
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.ACCEPT, resourceType = ResourceType.PULL_REQUEST)
public static Result accept(final String userName, final String projectName,
final long pullRequestNumber) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
final PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
// change it's state to block other accept request.
pullRequest.isMerging = true;
pullRequest.update();
final PullRequestEventMessage message = new PullRequestEventMessage(
UserApp.currentUser(), request(), project, pullRequest.toBranch);
if(project.isUsingReviewerCount && !pullRequest.isReviewed()) {
return badRequest(ErrorViews.BadRequest.render("pullRequest.not.enough.review.point"));
}
Promise<Void> promise = Akka.future(new Callable<Void>() {
@Override
public Void call() throws Exception {
pullRequest.merge(message);
// mark the end of the merge
pullRequest.endMerge();
pullRequest.update();
return null;
}
});
return async(promise.map(new Function<Void, Result>() {
@Override
public Result apply(Void v) throws Throwable {
return redirect(routes.PullRequestApp.pullRequest(userName, projectName,
pullRequestNumber));
}
}));
}
private static void addNotification(PullRequest pullRequest, State from, State to) {
NotificationEvent notiEvent = NotificationEvent.afterPullRequestUpdated(pullRequest, from, to);
PullRequestEvent.addFromNotificationEvent(notiEvent, pullRequest);
}
@Transactional
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.CLOSE, resourceType = ResourceType.PULL_REQUEST)
public static Result close(String userName, String projectName, Long pullRequestNumber) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
State beforeState = pullRequest.state;
pullRequest.close();
Call call = routes.PullRequestApp.pullRequest(userName, projectName, pullRequestNumber);
addNotification(pullRequest, beforeState, State.CLOSED);
return redirect(call);
}
@Transactional
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.REOPEN, resourceType = ResourceType.PULL_REQUEST)
public static Result open(String userName, String projectName, Long pullRequestNumber) {
Project project = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
if (pullRequest.isMerged() || pullRequest.isOpen()) {
return badRequest(ErrorViews.BadRequest.render());
}
State beforeState = pullRequest.state;
pullRequest.reopen();
Call call = routes.PullRequestApp.pullRequest(userName, projectName, pullRequestNumber);
addNotification(pullRequest, beforeState, State.OPEN);
PullRequestEventMessage message = new PullRequestEventMessage(
UserApp.currentUser(), request(), pullRequest);
Akka.system().actorOf(new Props(PullRequestMergingActor.class)).tell(message, null);
return redirect(call);
}
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.UPDATE, resourceType = ResourceType.PULL_REQUEST)
public static Result editPullRequestForm(String userName, String projectName, Long pullRequestNumber) throws IOException, GitAPIException {
Project toProject = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(toProject, pullRequestNumber);
Project fromProject = pullRequest.fromProject;
Form<PullRequest> editForm = new Form<>(PullRequest.class).fill(pullRequest);
List<GitBranch> fromBranches = new GitRepository(pullRequest.fromProject).getAllBranches();
List<GitBranch> toBranches = new GitRepository(pullRequest.toProject).getAllBranches();
return ok(edit.render("title.editPullRequest", editForm, fromProject, fromBranches, toBranches, pullRequest));
}
@Transactional
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.UPDATE, resourceType = ResourceType.PULL_REQUEST)
public static Result editPullRequest(String userName, String projectName, Long pullRequestNumber) {
Project toProject = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(toProject, pullRequestNumber);
Project fromProject = pullRequest.fromProject;
Form<PullRequest> pullRequestForm = new Form<>(PullRequest.class).bindFromRequest();
PullRequest updatedPullRequest = pullRequestForm.get();
if (pullRequest.body == null) {
return status(REQUEST_ENTITY_TOO_LARGE,
ErrorViews.RequestTextEntityTooLarge.render());
}
updatedPullRequest.toProject = toProject;
updatedPullRequest.fromProject = fromProject;
if(!updatedPullRequest.hasSameBranchesWith(pullRequest)) {
PullRequest sentRequest = PullRequest.findDuplicatedPullRequest(updatedPullRequest);
if(sentRequest != null) {
flash(Constants.WARNING, "pullRequest.duplicated");
return redirect(routes.PullRequestApp.editPullRequestForm(fromProject.owner, fromProject.name, pullRequestNumber));
}
}
pullRequest.updateWith(updatedPullRequest);
AbstractPostingApp.attachUploadFilesToPost(pullRequest.asResource());
return redirect(routes.PullRequestApp.pullRequest(toProject.owner, toProject.name, pullRequest.number));
}
@Transactional
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.UPDATE, resourceType = ResourceType.PULL_REQUEST)
public static Result deleteFromBranch(String userName, String projectName, Long pullRequestNumber) {
Project toProject = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(toProject, pullRequestNumber);
pullRequest.deleteFromBranch();
return redirect(routes.PullRequestApp.pullRequest(toProject.owner, toProject.name, pullRequestNumber));
}
@Transactional
@With(AnonymousCheckAction.class)
@IsAllowed(value = Operation.UPDATE, resourceType = ResourceType.PULL_REQUEST)
public static Result restoreFromBranch(String userName, String projectName, Long pullRequestNumber) {
Project toProject = Project.findByOwnerAndProjectName(userName, projectName);
PullRequest pullRequest = PullRequest.findOne(toProject, pullRequestNumber);
pullRequest.restoreFromBranch();
return redirect(routes.PullRequestApp.pullRequest(toProject.owner, toProject.name, pullRequestNumber));
}
private static ValidationResult validateBeforePullRequest(Project project) {
Result result = null;
boolean hasError = false;
if(ProjectUser.isGuest(project, UserApp.currentUser())) {
result = forbidden(ErrorViews.BadRequest.render("Guest is not allowed this request", project));
hasError = true;
}
return new ValidationResult(result, hasError);
}
@IsCreatable(ResourceType.REVIEW_COMMENT)
public static Result newComment(String ownerName, String projectName, Long pullRequestId,
String commitId) throws IOException, ServletException,
SVNException {
Form<CodeRange> codeRangeForm = new Form<>(CodeRange.class).bindFromRequest();
Form<ReviewComment> reviewCommentForm = new Form<>(ReviewComment.class)
.bindFromRequest();
Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
if (reviewCommentForm.hasErrors()) {
return badRequest(ErrorViews.BadRequest.render("error.validation", project));
}
PullRequest pullRequest = PullRequest.findById(pullRequestId);
if (pullRequest == null) {
return notFound(notfound.render("error.notfound", project, request().path()));
}
ReviewComment comment = reviewCommentForm.get();
comment.author = new UserIdent(UserApp.currentUser());
if (comment.thread == null) {
if (codeRangeForm.errors().isEmpty()) {
CodeCommentThread thread = new CodeCommentThread();
if (commitId != null) {
thread.commitId = commitId;
} else {
thread.commitId = pullRequest.mergedCommitIdTo;
thread.prevCommitId = pullRequest.mergedCommitIdFrom;
}
User codeAuthor = RepositoryService
.getRepository(project)
.getCommit(thread.commitId)
.getAuthor();
if (!codeAuthor.isAnonymous()) {
thread.codeAuthors.add(codeAuthor);
}
thread.codeRange = codeRangeForm.get();
comment.thread = thread;
} else {
// non-range
NonRangedCodeCommentThread thread = new NonRangedCodeCommentThread();
if (commitId != null) {
thread.commitId = commitId;
} else {
thread.commitId = pullRequest.mergedCommitIdTo;
thread.prevCommitId = pullRequest.mergedCommitIdFrom;
}
comment.thread = thread;
}
comment.thread.project = project;
comment.thread.state = CommentThread.ThreadState.OPEN;
comment.thread.createdDate = comment.createdDate;
comment.thread.author = comment.author;
pullRequest.addCommentThread(comment.thread);
} else {
comment.thread = CommentThread.find.byId(comment.thread.id);
}
comment.save();
pullRequest.update();
AbstractPostingApp.attachUploadFilesToPost(comment.asResource());
play.mvc.Call toView;
if (commitId != null) {
toView = routes.PullRequestApp.specificChange(ownerName, projectName,
pullRequest.number, commitId);
} else {
toView = routes.PullRequestApp.pullRequestChanges(ownerName, projectName,
pullRequest.number);
}
String urlToView = toView + "#comment-" + comment.id;
NotificationEvent.afterNewComment(UserApp.currentUser(), pullRequest, comment, urlToView);
return redirect(urlToView);
}
static class ValidationResult {
private Result result;
private boolean hasError;
ValidationResult(Result result, boolean hasError) {
this.result = result;
this.hasError = hasError;
}
public boolean hasError(){
return hasError;
}
public Result getResult() {
return result;
}
}
public static class SearchCondition {
public Project project;
public String filter;
public Long contributorId;
public int pageNum = Constants.DEFAULT_PAGE;
public Category category;
public SearchCondition setProject(Project project) {
this.project = project;
return this;
}
public SearchCondition setFilter(String filter) {
this.filter = filter;
return this;
}
public SearchCondition setContributorId(Long contributorId) {
this.contributorId = contributorId;
return this;
}
public SearchCondition setPageNum(int pageNum) {
this.pageNum = pageNum;
return this;
}
public SearchCondition setCategory(Category category) {
this.category = category;
return this;
}
@Override
public SearchCondition clone() throws CloneNotSupportedException {
SearchCondition clone = new SearchCondition();
clone.project = this.project;
clone.filter = this.filter;
clone.contributorId = this.contributorId;
clone.pageNum = this.pageNum;
clone.category = this.category;
return clone;
}
public String queryString() throws UnsupportedEncodingException {
List<String> queryStrings = new ArrayList<>();
if (StringUtils.isNotBlank(filter)) {
queryStrings.add("filter=" + URLEncoder.encode(filter, "UTF-8"));
}
if (category != Category.SENT && contributorId != null) {
queryStrings.add("contributorId=" + contributorId);
}
if (pageNum != Constants.DEFAULT_PAGE) {
queryStrings.add("pageNum=" + pageNum);
}
if (queryStrings.isEmpty()) {
return StringUtils.EMPTY;
}
return "?" + StringUtils.join(queryStrings, "&");
}
}
public enum Category {
OPEN("open", "toProject", "number", State.OPEN),
CLOSED("closed", "toProject", "received", State.CLOSED, State.MERGED),
SENT("sent", "fromProject", "created"),
ACCEPTED("accepted", "fromProject", "created", State.MERGED);
private Category(String code, String project, String order, State... states) {
this.code = code;
this.project = project;
this.order = order;
this.states = states;
}
private String code;
private String project;
private String order;
private State[] states;
public String code() {
return code;
}
public String project() {
return project;
}
public String order() {
return order;
}
public State[] states() {
return states;
}
}
}