package scrum.server.project;
import ilarkesto.base.Str;
import ilarkesto.base.Utl;
import ilarkesto.base.time.Date;
import ilarkesto.base.time.DateAndTime;
import ilarkesto.core.logging.Log;
import ilarkesto.io.IO;
import ilarkesto.persistence.AEntity;
import ilarkesto.velocity.ContextBuilder;
import ilarkesto.velocity.Velocity;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import scrum.client.wiki.HtmlContext;
import scrum.client.wiki.WikiModel;
import scrum.client.wiki.WikiParser;
import scrum.server.collaboration.Comment;
import scrum.server.collaboration.CommentDao;
import scrum.server.collaboration.Wikipage;
import scrum.server.common.BurndownChart;
import scrum.server.issues.Issue;
import scrum.server.pr.BlogEntry;
import scrum.server.release.Release;
import scrum.server.sprint.Sprint;
public class HomepageUpdater {
private static Log log = Log.get(HomepageUpdater.class);
private Project project;
private MyHtmlContext htmlContext;
private File templateDir;
private File outputDir;
private Velocity velocity;
private Properties properties;
public HomepageUpdater(Project project, String templatePath, String outputPath) {
super();
assert project != null;
this.project = project;
this.templateDir = new File(templatePath);
this.outputDir = new File(outputPath);
htmlContext = new MyHtmlContext(project);
velocity = new Velocity(templateDir);
loadProperties();
}
public HomepageUpdater(Project project) {
this(project, project.getHomepageVelocityDir(), project.getHomepageDir());
}
public void processAll() {
processDefaultTemplates();
processIssueTemplates();
processStoryTemplates();
processReleaseTemplates();
processBlogEntryTemplates();
createSprintBurndownChart(700, 200);
}
private void processDefaultTemplates() {
ContextBuilder context = new ContextBuilder();
fillProject(context.putSubContext("project"));
fillWiki(context.putSubContext("wiki"));
fillBlog(context.putSubContext("blog"));
fillSprintBacklog(context.putSubContext("sprintBacklog"));
fillProductBacklog(context.putSubContext("productBacklog"));
fillBugs(context);
fillIdeas(context);
fillClosedIssues(context);
fillReleases(context);
fillIssues(context);
fillStories(context);
File[] templateFiles = templateDir.listFiles();
if (templateFiles == null) return;
for (File templateFile : templateFiles) {
String templateName = templateFile.getName();
if (!templateName.endsWith(".vm")) continue;
if (templateName.equals(Velocity.LIB_TEMPLATE_NAME)) continue;
if (templateName.startsWith("iss.")) continue;
if (templateName.startsWith("blg.")) continue;
if (templateName.startsWith("sto.")) continue;
String outputFileName = Str.removeSuffix(templateName, ".vm");
velocity.processTemplate(templateName, new File(outputDir.getPath() + "/" + outputFileName), context);
}
}
private void processEntityTemplate(ContextBuilder context, String reference) {
fillProject(context.putSubContext("project"));
fillProductBacklog(context.putSubContext("productBacklog"));
fillSprintBacklog(context.putSubContext("sprintBacklog"));
fillWiki(context.putSubContext("wiki"));
String prefix = reference.substring(0, 3);
File[] templateFiles = templateDir.listFiles();
if (templateFiles == null) return;
for (File templateFile : templateFiles) {
String templateName = templateFile.getName();
if (!templateName.endsWith(".vm")) continue;
if (!templateName.startsWith(prefix + ".")) continue;
String outputFileName = Str.removeSuffix(templateName, ".vm");
outputFileName = Str.removePrefix(outputFileName, prefix + ".");
outputFileName = reference + "." + outputFileName;
velocity.processTemplate(templateName, new File(outputDir.getPath() + "/" + outputFileName), context);
}
}
private void processReleaseTemplates() {
List<Release> releases = new ArrayList<Release>(project.getReleases());
Collections.sort(releases, Release.DATE_REVERSE_COMPARATOR);
for (Release release : releases) {
if (!release.isReleased()) continue;
ContextBuilder context = new ContextBuilder();
fillRelease(context.putSubContext("release"), release);
String reference = release.getReference();
processEntityTemplate(context, reference);
}
}
public void processIssueTemplates() {
List<Issue> issues = new ArrayList<Issue>(project.getPublishedIssues());
for (Issue issue : issues) {
processIssueTemplate(issue);
}
}
public void processEntityTemplate(AEntity entity) {
if (entity instanceof Issue) {
processIssueTemplate((Issue) entity);
return;
}
if (entity instanceof Requirement) {
processStoryTemplate((Requirement) entity);
return;
}
if (entity instanceof BlogEntry) {
processBlogEntryTemplate((BlogEntry) entity);
return;
}
}
public void processIssueTemplate(Issue issue) {
ContextBuilder context = new ContextBuilder();
fillIssue(context.putSubContext("issue"), issue);
processEntityTemplate(context, issue.getReference());
}
private void processStoryTemplates() {
List<Requirement> requirements = new ArrayList<Requirement>(project.getRequirements());
for (Requirement requirement : requirements) {
if (requirement.isClosed()) continue;
processStoryTemplate(requirement);
}
}
private void processStoryTemplate(Requirement requirement) {
ContextBuilder context = new ContextBuilder();
fillStory(context.putSubContext("story"), requirement);
processEntityTemplate(context, requirement.getReference());
}
private void processBlogEntryTemplates() {
List<BlogEntry> entries = new ArrayList<BlogEntry>(project.getBlogEntrys());
for (BlogEntry entry : entries) {
if (!entry.isPublished()) continue;
processBlogEntryTemplate(entry);
}
}
private void processBlogEntryTemplate(BlogEntry entry) {
ContextBuilder context = new ContextBuilder();
fillBlogEntry(context.putSubContext("entry"), entry);
processEntityTemplate(context, entry.getReference());
}
private void createSprintBurndownChart(int width, int height) {
byte[] imageData = BurndownChart.createBurndownChartAsByteArray(project.getCurrentSprint(), width, height);
IO.copyDataToFile(imageData,
new File(outputDir.getPath() + "/sprint-burndown-" + width + "x" + height + ".png"));
}
private void fillIssues(ContextBuilder context) {
List<Issue> issues = new ArrayList<Issue>(project.getBugsInCurrentRelease());
Collections.sort(issues, Issue.ACCEPT_DATE_COMPARATOR);
for (Issue issue : project.getIssues()) {
if (!issue.isPublished()) continue;
fillIssue(context.addSubContext("issues"), issue);
}
}
private void fillBugs(ContextBuilder context) {
List<Issue> issues = new ArrayList<Issue>(project.getBugsInCurrentRelease());
Collections.sort(issues, Issue.ACCEPT_DATE_COMPARATOR);
for (Issue issue : issues) {
fillIssue(context.addSubContext("bugs"), issue);
}
}
private void fillIdeas(ContextBuilder context) {
List<Issue> issues = new ArrayList<Issue>(project.getOpenIdeas());
Collections.sort(issues, Issue.ACCEPT_DATE_COMPARATOR);
for (Issue issue : issues) {
fillIssue(context.addSubContext("ideas"), issue);
}
}
private void fillClosedIssues(ContextBuilder context) {
List<Issue> issues = new ArrayList<Issue>(project.getClosedIssues());
Collections.sort(issues, Issue.CLOSE_DATE_COMPARATOR);
for (Issue issue : issues) {
fillIssue(context.addSubContext("closedIssues"), issue);
}
}
private void fillReleases(ContextBuilder context) {
List<Release> releases = new ArrayList<Release>(project.getReleases());
Collections.sort(releases, Release.DATE_REVERSE_COMPARATOR);
for (Release release : releases) {
if (!release.isReleased()) continue;
fillRelease(context.addSubContext("releases"), release);
}
}
private void fillIssue(ContextBuilder context, Issue issue) {
context.put("id", issue.getId());
context.put("reference", issue.getReference());
context.put("label", issue.getLabel());
context.put("description", wiki2html(issue.getDescription()));
context.put("statement", wiki2html(issue.getStatement()));
context.put("statusText", issue.getStatusText());
if (issue.isOwnerSet()) context.put("owner", issue.getOwner().getName());
if (issue.isFixed()) context.put("fixed", "true");
fillComments(context, issue);
}
private void fillComments(ContextBuilder context, AEntity entity) {
CommentDao commentDao = (CommentDao) entity.getDaoService().getDaoByClass(Comment.class);
Collection<Comment> comments = commentDao.getPublishedCommentsByParent(entity);
comments = Utl.sort(comments);
for (Comment comment : comments) {
fillComment(context.addSubContext("comments"), comment);
}
}
private void fillComment(ContextBuilder context, Comment comment) {
context.put("id", comment.getId());
context.put("text", wiki2html(comment.getText()));
context.put("author", comment.getAuthorLabel());
context.put("date", comment.getDateAndTime()
.toString(DateAndTime.FORMAT_WEEKDAY_LONGMONTH_DAY_YEAR_HOUR_MINUTE));
}
private void fillRelease(ContextBuilder context, Release release) {
context.put("id", release.getId());
context.put("reference", release.getReference());
context.put("label", release.getLabel());
context.put("note", wiki2html(release.getNote()));
context.put("releaseNotes", wiki2html(release.getReleaseNotes()));
context.put("releaseDate", release.getReleaseDate());
context.put("released", release.isReleased());
context.put("major", release.isMajor());
context.put("bugfix", release.isBugfix());
fillComments(context, release);
}
private void fillBlog(ContextBuilder context) {
List<BlogEntry> entries = new ArrayList<BlogEntry>(project.getBlogEntrys());
Collections.sort(entries);
for (BlogEntry entry : entries) {
if (!entry.isPublished()) continue;
fillBlogEntry(context.addSubContext("entries"), entry);
}
}
private void fillBlogEntry(ContextBuilder context, BlogEntry entry) {
context.put("id", entry.getId());
context.put("reference", entry.getReference());
context.put("title", entry.getTitle());
context.put("text", wiki2html(entry.getText()));
context.put("plainText", wiki2text(entry.getText()));
DateAndTime date = entry.getDateAndTime();
context.put("date", date.toString(Date.FORMAT_LONGMONTH_DAY_YEAR));
context.put("rssDate", date.toString(DateAndTime.FORMAT_RFC822));
fillComments(context, entry);
}
private void fillSprintBacklog(ContextBuilder context) {
Sprint sprint = project.getCurrentSprint();
context.put("label", sprint.getLabel());
context.put("goal", wiki2html(sprint.getGoal()));
context.put("begin", sprint.getBegin().toString(Date.FORMAT_LONGMONTH_DAY_YEAR));
context.put("end", sprint.getEnd().toString(Date.FORMAT_LONGMONTH_DAY_YEAR));
Release release = sprint.getRelease();
if (release != null) context.put("release", release.getLabel());
List<Requirement> requirements = new ArrayList<Requirement>(sprint.getRequirements());
Collections.sort(requirements, project.getRequirementsOrderComparator());
for (Requirement requirement : requirements) {
fillStory(context.addSubContext("stories"), requirement);
}
}
private void fillProductBacklog(ContextBuilder context) {
List<Requirement> requirements = new ArrayList<Requirement>(project.getRequirements());
Collections.sort(requirements, project.getRequirementsOrderComparator());
for (Requirement requirement : requirements) {
if (requirement.isClosed() || requirement.isInCurrentSprint()) continue;
fillStory(context.addSubContext("stories"), requirement);
}
}
private void fillStories(ContextBuilder context) {
for (Requirement requirement : project.getRequirements()) {
if (requirement.isClosed()) continue;
fillStory(context.addSubContext("stories"), requirement);
}
}
private void fillStory(ContextBuilder context, Requirement requirement) {
context.put("id", requirement.getId());
context.put("reference", requirement.getReference());
context.put("label", requirement.getLabel());
context.put("description", wiki2html(requirement.getDescription()));
context.put("testDescription", wiki2html(requirement.getTestDescription()));
if (requirement.isEstimatedWorkSet() && !requirement.isDirty())
context.put("estimatedWork", requirement.getEstimatedWorkAsString());
fillComments(context, requirement);
}
private void fillWiki(ContextBuilder context) {
for (Wikipage page : project.getWikipages()) {
context.put(page.getName(), wiki2html(page.getText()));
}
}
private void fillProject(ContextBuilder context) {
context.put("id", project.getId());
context.put("label", project.getLabel());
context.put("vision", project.getVision());
context.put("shortDescription", project.getShortDescription());
context.put("description", wiki2html(project.getDescription()));
context.put("longDescription", wiki2html(project.getLongDescription()));
Release currentRelease = project.getCurrentRelease();
context.put("currentRelease", currentRelease == null ? "?" : currentRelease.getLabel());
}
public String wiki2html(String wikitext) {
if (Str.isBlank(wikitext)) return null;
WikiParser wikiParser = new WikiParser(wikitext);
WikiModel model = wikiParser.parse(false);
return model.toHtml(htmlContext);
}
public static String wiki2text(String wikitext) {
if (Str.isBlank(wikitext)) return null;
return wikitext;
}
private void loadProperties() {
File file = new File(outputDir.getPath() + "/kunagi.properties");
if (!file.exists()) file = new File(templateDir.getPath() + "/kunagi.properties");
if (!file.exists()) {
properties = new Properties();
return;
}
properties = IO.loadProperties(file, IO.UTF_8);
}
private static class MyHtmlContext implements HtmlContext {
private Project project;
public MyHtmlContext(Project project) {
super();
this.project = project;
}
@Override
public String getEntityReferenceHrefOrOnclickAParameter(String reference) {
return "href=\"" + reference + ".html\"";
}
@Override
public String getDownloadUrlByReference(String reference) {
return reference;
}
@Override
public String getEntityLabelByReference(String reference) {
AEntity entity = project.getEntityByReference(reference);
return entity == null ? null : entity.toString();
}
}
}