/*
* Copyright 2008, 2009, 2010 Witoslaw Koczewski, Artjom Kochtchi, Fabian Hager, Kacper Grubalski.
*
* This file is part of Kunagi.
*
* Kunagi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Kunagi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with Foobar. If not, see
* <http://www.gnu.org/licenses/>.
*/
package scrum.server;
import ilarkesto.base.Tm;
import ilarkesto.base.Url;
import ilarkesto.base.time.DateAndTime;
import ilarkesto.base.time.Time;
import ilarkesto.concurrent.TaskManager;
import ilarkesto.core.base.Str;
import ilarkesto.core.logging.Log;
import ilarkesto.di.app.WebApplicationStarter;
import ilarkesto.email.Eml;
import ilarkesto.fp.FP;
import ilarkesto.fp.Function;
import ilarkesto.gwt.server.AGwtConversation;
import ilarkesto.io.IO;
import ilarkesto.webapp.AWebApplication;
import ilarkesto.webapp.AWebSession;
import ilarkesto.webapp.DestroyTimeoutedSessionsTask;
import ilarkesto.webapp.Servlet;
import java.io.File;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import scrum.client.ApplicationInfo;
import scrum.client.UsersStatusData;
import scrum.client.admin.SystemMessage;
import scrum.server.admin.DeleteDisabledUsersTask;
import scrum.server.admin.DisableUsersWithUnverifiedEmailsTask;
import scrum.server.admin.SystemConfig;
import scrum.server.admin.User;
import scrum.server.admin.UserDao;
import scrum.server.common.BurndownChart;
import scrum.server.project.DeleteOldProjectsTask;
import scrum.server.project.HomepageUpdaterTask;
import scrum.server.project.Project;
public class ScrumWebApplication extends GScrumWebApplication {
private static final Log log = Log.get(ScrumWebApplication.class);
private boolean testMode;
private BurndownChart burndownChart;
private ScrumConfig config;
private ScrumEntityfilePreparator entityfilePreparator;
private SystemMessage systemMessage;
// --- composites ---
public BurndownChart getBurndownChart() {
if (burndownChart == null) {
burndownChart = new BurndownChart();
burndownChart.setSprintDao(getSprintDao());
burndownChart.setProjectDao(getProjectDao());
}
return burndownChart;
}
public SystemConfig getSystemConfig() {
return getSystemConfigDao().getSystemConfig();
}
public ScrumConfig getConfig() {
if (config == null) {
config = new ScrumConfig(getApplicationDataDir());
}
return config;
}
public ScrumEntityfilePreparator getEntityfilePreparator() {
if (entityfilePreparator == null) {
entityfilePreparator = new ScrumEntityfilePreparator();
entityfilePreparator.setBackupDir(getApplicationDataDir() + "/backup/entities");
}
return entityfilePreparator;
}
public ApplicationInfo getApplicationInfo() {
User admin = getUserDao().getUserByName("admin");
boolean defaultAdminPassword = false;
if (admin != null && admin.matchesPassword(scrum.client.admin.User.INITIAL_PASSWORD))
defaultAdminPassword = true;
return new ApplicationInfo("kunagi", getReleaseLabel(), getBuild(), getDeploymentStage(), defaultAdminPassword);
}
// --- ---
@Override
public void ensureIntegrity() {
if (getConfig().isStartupDelete()) {
log.warn("DELETING ALL ENTITIES (set startup.delete=false in config.properties to prevent this behavior)");
IO.delete(getApplicationDataDir() + "/entities");
}
super.ensureIntegrity();
}
@Override
protected void onStartWebApplication() {
Log.setDebugEnabled(isDevelopmentMode() || getConfig().isLoggingDebug());
deleteOldBackupFiles(getApplicationDataDir() + "/backup");
String url = getConfig().getUrl();
if (!Str.isBlank(url)) getSystemConfig().setUrl(url);
if (getUserDao().getEntities().isEmpty()) {
String password = getConfig().getInitialPassword();
log.warn("No users. Creating initial user <admin> with password <" + password + ">");
User admin = getUserDao().postUserWithDefaultPassword("admin");
admin.setPassword(password);
admin.setAdmin(true);
getTransactionService().commit();
}
getProjectDao().scanFiles();
getTransactionService().commit();
// test data
if ((isDevelopmentMode() || getConfig().isStageIntegration()) && getProjectDao().getEntities().isEmpty())
createTestData();
}
public String createUrl(String relativePath) {
if (relativePath == null) relativePath = "";
String prefix = getConfig().getUrl();
if (Str.isBlank(prefix)) return relativePath;
if (prefix.endsWith("/")) {
if (relativePath.startsWith("/")) return prefix.substring(0, prefix.length() - 1) + relativePath;
return prefix + relativePath;
}
if (relativePath.startsWith("/")) return prefix + relativePath;
return prefix + "/" + relativePath;
}
private void createTestData() {
log.warn("Creating test data");
getUserDao().postUserWithDefaultPassword("homer");
getUserDao().postUserWithDefaultPassword("cartman");
getUserDao().postUserWithDefaultPassword("duke");
getUserDao().postUserWithDefaultPassword("spinne");
getProjectDao().postExampleProject(getUserDao().getUserByName("admin"), getUserDao().getUserByName("cartman"),
getUserDao().getUserByName("admin"));
getTransactionService().commit();
}
@Override
protected void scheduleTasks(TaskManager tm) {
tm.scheduleWithFixedDelay(autowire(new DestroyTimeoutedSessionsTask()), Tm.MINUTE);
tm.scheduleWithFixedDelay(autowire(new HomepageUpdaterTask()), Tm.HOUR);
if (getConfig().isDisableUsersWithUnverifiedEmails())
tm.scheduleWithFixedDelay(autowire(new DisableUsersWithUnverifiedEmailsTask()), Tm.HOUR);
if (getConfig().isDeleteOldProjects())
tm.scheduleWithFixedDelay(autowire(new DeleteOldProjectsTask()), Tm.SECOND, Tm.HOUR * 25);
if (getConfig().isDeleteDisabledUsers())
tm.scheduleWithFixedDelay(autowire(new DeleteDisabledUsersTask()), Tm.MINUTE * 3, Tm.HOUR * 26);
}
@Override
protected String getDevelopmentModeApplicationDataDir() {
if (testMode) return new File("test-output/runtimedata").getAbsolutePath();
return super.getDevelopmentModeApplicationDataDir();
}
public void setTestMode(boolean testMode) {
this.testMode = testMode;
}
@Override
protected void onShutdownWebApplication() {}
@Override
public Url getHomeUrl() {
return new Url("index.html");
}
public String getBaseUrl() {
return getConfig().isStageIntegration() ? "https://servisto.de/scrum-latest/" : getSystemConfig().getUrl();
}
private UserDao userDao;
public UserDao getUserDao() {
if (userDao == null) {
userDao = new UserDao();
autowire(userDao);
}
return userDao;
}
public boolean isAdminPasswordDefault() {
User admin = userDao.getUserByName("admin");
if (admin == null) return false;
return admin.matchesPassword(scrum.client.admin.User.INITIAL_PASSWORD);
}
public Set<GwtConversation> getConversationsByProject(Project project, GwtConversation exception) {
Set<GwtConversation> ret = new HashSet<GwtConversation>();
for (Object element : getGwtConversations()) {
if (element == exception) continue;
GwtConversation conversation = (GwtConversation) element;
if (project != null && project.equals(conversation.getProject())) ret.add(conversation);
}
return ret;
}
public Set<User> getConversationUsersByProject(Project project) {
Set<User> ret = new HashSet<User>();
for (GwtConversation conversation : getConversationsByProject(project, null)) {
User user = conversation.getSession().getUser();
if (user != null) ret.add(user);
}
return ret;
}
public void updateOnlineTeamMembers(Project project, GwtConversation exclude) {
if (project == null) return;
Set<User> users = getConversationUsersByProject(project);
Set<String> userIds = new HashSet<String>(FP.foreach(users, new Function<User, String>() {
@Override
public String eval(User user) {
return user.getId();
}
}));
project.getUsersStatus().setOnlineUsers(userIds);
log.debug("Updated online team members on project:", project, "->", project.getUsersStatus());
sendUsersStatusToClients(project, exclude);
}
private void sendUsersStatusToClients(Project project, GwtConversation exclude) {
UsersStatusData status = project.getUsersStatus();
for (GwtConversation conversation : getConversationsByProject(project, exclude)) {
conversation.getNextData().usersStatus = status;
}
}
public void setUsersSelectedEntities(Project project, GwtConversation conversation, Set<String> ids) {
UsersStatusData usersStatus = project.getUsersStatus();
WebSession session = conversation.getSession();
User user = session.getUser();
String userId = user.getId();
usersStatus.setUsersSelectedEntities(userId, ids);
sendUsersStatusToClients(project, conversation);
}
@Override
protected AWebSession createWebSession(HttpServletRequest httpRequest) {
WebSession session = new WebSession(context, httpRequest);
autowire(session);
return session;
}
public String getDeploymentStage() {
if (isDevelopmentMode()) return ApplicationInfo.DEPLOYMENT_STAGE_DEVELOPMENT;
if (getConfig().isStageIntegration()) return ApplicationInfo.DEPLOYMENT_STAGE_INTEGRATION;
return ApplicationInfo.DEPLOYMENT_STAGE_PRODUCTION;
}
public String getBuild() {
Properties properties = IO.loadPropertiesFromClasspath("scrum/server/build.properties");
String date = properties.getProperty("date");
if ("@build-date@".equals(date)) date = Time.now().toString();
return date;
}
public void updateSystemMessage(SystemMessage systemMessage) {
this.systemMessage = systemMessage;
for (AGwtConversation conversation : getGwtConversations()) {
log.debug("Sending SystemMessage to:", conversation);
((GwtConversation) conversation).getNextData().systemMessage = systemMessage;
}
}
public SystemMessage getSystemMessage() {
return systemMessage;
}
public static ScrumWebApplication get() {
return (ScrumWebApplication) AWebApplication.get();
}
public static synchronized ScrumWebApplication get(ServletConfig servletConfig) {
if (AWebApplication.isStarted()) return get();
return (ScrumWebApplication) WebApplicationStarter.startWebApplication(ScrumWebApplication.class.getName(),
Servlet.getContextPath(servletConfig));
}
public void triggerRegisterNotification(User user) {
StringBuilder sb = new StringBuilder();
sb.append("Kunagi URL: ").append(getBaseUrl()).append("\n");
sb.append("Name: ").append(user.getName()).append("\n");
sb.append("Email: ").append(user.getEmail()).append("\n");
sb.append("Date/Time: ").append(DateAndTime.now()).append("\n");
sendEmail(null, null, user.getLabel() + " registered on " + getBaseUrl(), sb.toString());
}
public void sendEmail(String from, String to, String subject, String text) {
Session session = createSmtpSession();
if (session == null) return;
SystemConfig config = getSystemConfig();
if (Str.isBlank(from)) from = config.getSmtpFrom();
if (Str.isBlank(from)) {
log.error("Missing configuration: smtpFrom");
return;
}
if (Str.isBlank(to)) to = config.getAdminEmail();
if (Str.isBlank(to)) {
log.error("Missing configuration: adminEmail");
return;
}
if (Str.isBlank(subject)) subject = "Kunagi";
MimeMessage message = Eml.createTextMessage(session, subject, text, from, to);
Eml.sendSmtpMessage(session, message);
}
public Session createSmtpSession() {
SystemConfig config = getSystemConfig();
String smtpServer = config.getSmtpServer();
Integer smtpPort = config.getSmtpPort();
boolean smtpTls = config.isSmtpTls();
if (smtpServer == null) {
log.error("Missing configuration: smtpServer");
return null;
}
return Eml.createSmtpSession(smtpServer, smtpPort, smtpTls, config.getSmtpUser(), config.getSmtpPassword());
}
}