/*
Copyright 2012 Christian Prause and Fraunhofer FIT
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 net.sf.collabreview.other;
import net.sf.collabreview.core.CollabReview;
import net.sf.collabreview.core.Lifecycle;
import net.sf.collabreview.core.Repository;
import net.sf.collabreview.core.configuration.ConfigurationData;
import net.sf.collabreview.core.mails.Mail;
import net.sf.collabreview.hooks.SetReviewHook;
import net.sf.collabreview.repository.Review;
import net.sf.collabreview.reputation.MetricDelta;
import net.sf.collabreview.reputation.ReputationMetric;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import java.util.*;
/**
* Send notification mails during the code camp...
*
* @author Christian Prause (prause)
* @date 2011-11-11 15:55
*/
public class InternalCodeCampMails implements Lifecycle {
/**
* Apache commons logging logger for class InternalCodeCampMails.
*/
private static final Log logger = LogFactory.getLog(InternalCodeCampMails.class);
/**
* The CollabReview instance that this object belongs to.
*/
private CollabReview collabReview = null;
/**
* The MessageSender TimerTask that is currently scheduled for execution, or null if no such task is scheduled.
*/
private MessageSender mailSender = null;
/**
* How long to delay the message before it is sent by the mailSender task.
*/
private long messageDelay = 300;
/**
* List of participants who are participating in the game and that should receive a mail.
*/
private Set<String> participants = new HashSet<String>();
/**
* Who should receive the mail with the new technical debt scores (usually a mailing list?).
* Needs to be configured in configuration.
*/
private InternetAddress mailRecipient;
/**
* TimerTask responsible for sending the daily mails.
*/
private class MessageSender extends TimerTask {
/**
* The review that caused this MessageSender to be scheduled for execution.
*/
private Review review;
private MessageSender(Review review) {
this.review = review;
}
@Override
public void run() {
logger.info("Sending updated reputation scores in mail now...");
try {
sendMails();
} catch (Exception e) {
logger.error("Uncaught exception when sending daily mails", e);
}
}
private void sendMails() {
MetricDelta deltaDebt = (MetricDelta) collabReview.getReputationMetricManager().findReputationMetric("delta");
assert deltaDebt != null;
ReputationMetric quality = collabReview.getReputationMetricManager().findReputationMetric("qlybonqty");
ReputationMetric technicalDebt = collabReview.getReputationMetricManager().findReputationMetric("techdebt");
assert technicalDebt != null;
Map<String, Float> scores = new TreeMap<String, Float>(quality.getAuthorScores());
scores.keySet().retainAll(participants);
logger.debug("Participant scores: " + scores);
if (mailRecipient == null) {
mailRecipient = collabReview.getMailManager().getAdministrator();
}
Mail listMail = collabReview.getMailManager().newMail("Javadoc reputation scores overview", mailRecipient.toString());
assert review != null;
String body = "Achtung liebe Entwickler,\n\n" +
"es gibt eine kleine Änderung in der Berechnung der Punkte, weil mehrfach\n" +
"darum gebeten wurde, doch auch Quantität mit einzubeziehen. Deshalb sind\n" +
"die neuen Punkte jetzt inklusive einem Bonus von maximal 5 Punkten, der\n" +
"sich anteilig an dem Beitrag des Entwicklers der am zweitmeisten beige-\n" +
"tragen hat, bestimmt. Das heißt: hast du halb so viel wie derzeit Mark\n" +
"beigetragen, dann ist dein Bonus 2,5.\n\n" +
"Und jetzt weiter wie gehabt:\n" +
"Eine Änderung wurde in das SourceForge repository übertragen und \n" +
"anschließend auf Konformität zu den Java Coding Conventions überprüft.\n" +
"Diese Mail informiert euch über eure aktuelle Code-Qualitäts-Reputation\n" +
"nach dieser Änderung.\n\n" +
"Die technische Schuld beträgt nun insgesamt " + toEuroString(technicalDebt.sum()) +
" (Veränderung: " + toEuroString(deltaDebt.sum()) + ").\n\n" +
getRankingTableString(scores, technicalDebt.getAuthorScores(), deltaDebt.getAuthorScores()) + "\n\n" +
"Die Überprüfung einer der Dateien im Änderungssatz ergab (beispielhaft):\n" +
" Datei: " + review.getArtifactIdentifier().getName() + "\n" +
" Bewertung: " + review.getRating() + "\n" +
" Bewertet durch: " + review.getAuthorName() + "\n" +
" Erklärung: " + review.getReviewText() + "\n\n";
listMail.setBody(body);
listMail.send();
deltaDebt.setNewBaseline();
}
}
private String toEuroString(Float value) {
int integerValue = value != null ? Math.round(value / 100) : 0;
if (integerValue == 0) {
return "---";
}
return (integerValue > 0 ? "+" : "") + integerValue + "00EUR";
}
private String getRankingTableString(Map<String, Float> scores, Map<String, Float> technicalDebt, Map<String, Float> deltaDebts) {
String table = "Javadoc Reputations-Rangliste:\n";
int rank = 1;
for (String otherParticipant : scores.keySet()) {
table += String.format(" %2d. %s: %.2f Punkte, Technische Schuld: %s (Veränderung: %s)\n",
rank, otherParticipant,
scores.get(otherParticipant),
toEuroString(technicalDebt.get(otherParticipant)),
toEuroString(deltaDebts.get(otherParticipant))
);
rank++;
}
return table;
}
@Override
public void start() {
logger.debug("Registering SetReviewHook to enable sending of status mails");
collabReview.getRepository().registerSetReviewHook(new SetReviewHook() {
@Override
public synchronized void reviewUpdated(Repository repository, Review oldReview, Review newReview) {
logger.debug("Review updated, scheduling MessageSender to run in " + messageDelay / 1000 + "s");
if (mailSender != null) {
mailSender.cancel();
}
mailSender = new MessageSender(newReview);
collabReview.getWorkerDaemon().schedule(mailSender, messageDelay);
}
@Override
public int getPriority() {
return PRIORITY_VERY_LOW;
}
});
}
@Override
public void stop() {
logger.info("Cancelling outstanding sending of mail");
if (mailSender != null) {
mailSender.cancel();
}
}
@Override
public void configure(CollabReview collabReview, ConfigurationData config) {
this.collabReview = collabReview;
assert this.collabReview != null;
logger.debug("<participants/>: " + Arrays.toString(config.getValue("participants", "").split(";")));
for (String participant : config.getValue("participants", "").split(";")) {
participant = participant.trim();
if ("".equals(participant)) {
continue;
}
participants.add(participant);
}
if (config.getValue("messageDelay") != null) {
messageDelay = Integer.parseInt(config.getValue("messageDelay")) * 1000l;
}
if (config.getValue("mailRecipient") != null) {
try {
mailRecipient = new InternetAddress(config.getValue("mailRecipient"));
} catch (AddressException e) {
logger.error("Not a valid email address: " + config.getValue("mailRecipient"));
}
}
}
}