<|start_filename|>src/main/java/org/example/kickoff/business/exception/InvalidTokenException.java<|end_filename|>
package org.example.kickoff.business.exception;

/**
 * Thrown when login token does not exist in DB or is expired.
 */
public class InvalidTokenException extends CredentialsException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/model/Role.java<|end_filename|>
package org.example.kickoff.model;

public enum Role {

	// Viewing stuff.
	VIEW_USER_PAGES,
	VIEW_ADMIN_PAGES,

	// Editing stuff.
	EDIT_OWN_PROFILE,
	EDIT_PROFILES,

	// Actions.
	ACCESS_API;

}

<|start_filename|>src/main/java/org/example/kickoff/business/BackgroundJobs.java<|end_filename|>
package org.example.kickoff.business;

import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.example.kickoff.business.service.LoginTokenService;

@Singleton
public class BackgroundJobs {

	@PersistenceContext
	private EntityManager entityManager;

	@Inject
	private LoginTokenService loginTokenService;

	@Schedule(dayOfWeek = "*", hour = "*", minute = "0", second = "0", persistent = false)
	public void hourly() {
		loginTokenService.removeExpired();
	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/admin/users/EditUserBacking.java<|end_filename|>
package org.example.kickoff.view.admin.users;

import static org.omnifaces.util.Faces.redirect;

import java.io.IOException;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;
import org.omnifaces.cdi.Param;

@Named
@RequestScoped
public class EditUserBacking {

	@Inject @Param(name="id")
	private User user;

	@Inject
	private UserService userService;

	public void save() throws IOException {
		userService.update(user);
		redirect("admin/users");
	}

	public User getUser() {
		return user;
	}

}

<|start_filename|>src/main/java/org/example/kickoff/model/Credentials.java<|end_filename|>
package org.example.kickoff.model;

import static org.hibernate.annotations.CacheConcurrencyStrategy.TRANSACTIONAL;
import static org.omnifaces.utils.security.MessageDigests.digest;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;

import org.hibernate.annotations.Cache;
import org.omnifaces.persistence.model.GeneratedIdEntity;

@Entity
public class Credentials extends GeneratedIdEntity<Long> {

	private static final long serialVersionUID = 1L;

	private static final int HASH_LENGTH = 32;
	private static final int SALT_LENGTH = 40;

	@ManyToOne(optional = false)
	@Cache(usage = TRANSACTIONAL)
	private @NotNull User user;

	@Column(length = HASH_LENGTH, nullable = false)
	private @NotNull byte[] passwordHash;

	@Column(length = SALT_LENGTH, nullable = false)
	private @NotNull byte[] salt = new byte[SALT_LENGTH];

	public void setUser(User user) {
		user.setCredentials(this);
		this.user = user;
	}

	public void setPassword(String password) {
		ThreadLocalRandom.current().nextBytes(salt);
		passwordHash = hash(password);
	}

	public boolean isValid(String password) {
		return Arrays.equals(passwordHash, hash(password));
	}

	private byte[] hash(String password) {
		return digest(password, salt, "SHA-256");
	}

}

<|start_filename|>src/main/java/org/example/kickoff/model/User.java<|end_filename|>
package org.example.kickoff.model;

import static java.util.stream.Collectors.toSet;
import static javax.persistence.CascadeType.ALL;
import static javax.persistence.EnumType.STRING;
import static javax.persistence.FetchType.EAGER;
import static javax.persistence.FetchType.LAZY;
import static org.hibernate.annotations.CacheConcurrencyStrategy.TRANSACTIONAL;

import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.example.kickoff.model.validator.Email;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.Formula;
import org.omnifaces.persistence.model.TimestampedEntity;

@Entity
public class User extends TimestampedEntity<Long> {

	private static final long serialVersionUID = 1L;

	public static final int EMAIL_MAXLENGTH = 254;
	public static final int NAME_MAXLENGTH = 32;

	@Column(length = EMAIL_MAXLENGTH, nullable = false, unique = true)
	private @NotNull @Size(max = EMAIL_MAXLENGTH) @Email String email;

	@Column(length = NAME_MAXLENGTH, nullable = false)
	private @NotNull @Size(max = NAME_MAXLENGTH) String firstName;

	@Column(length = NAME_MAXLENGTH, nullable = false)
	private @NotNull @Size(max = NAME_MAXLENGTH) String lastName;

	@Formula("CONCAT(firstName, ' ', lastName)")
	private String fullName;

	/*
	 * TODO: implement.
	 */
	@Column(nullable = false)
	private boolean emailVerified = true; // For now.

	@OneToOne(mappedBy = "user", fetch = LAZY, cascade = ALL)
	private Credentials credentials;

	@OneToMany(mappedBy = "user", fetch = LAZY, cascade = ALL, orphanRemoval = true)
	@Cache(usage = TRANSACTIONAL)
	private List<LoginToken> loginTokens = new ArrayList<>();

	@ElementCollection(fetch = EAGER)
	private @Enumerated(STRING) Set<Group> groups = new HashSet<>();

	@Column
	private Instant lastLogin;

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getFullName() {
		return fullName;
	}

	public boolean isEmailVerified() {
		return emailVerified;
	}

	public void setEmailVerified(boolean emailVerified) {
		this.emailVerified = emailVerified;
	}

	public Credentials getCredentials() {
		return credentials;
	}

	public void setCredentials(Credentials credentials) {
		this.credentials = credentials;
	}

	public List<LoginToken> getLoginTokens() {
		return loginTokens;
	}

	public Set<Group> getGroups() {
		return groups;
	}

	public void setGroups(Set<Group> groups) {
		this.groups = groups;
	}

	public Instant getLastLogin() {
		return lastLogin;
	}

	public void setLastLogin(Instant lastLogin) {
		this.lastLogin = lastLogin;
	}

	@Transient
	public Set<Role> getRoles() {
		return groups.stream().flatMap(g -> g.getRoles().stream()).collect(toSet());
	}

	@Transient
	public Set<String> getRolesAsStrings() {
		return getRoles().stream().map(Role::name).collect(toSet());
	}

}

<|start_filename|>src/test/java/org/example/kickoff/arquillian/ArquillianDBUnitTestBase.java<|end_filename|>
package org.example.kickoff.arquillian;

import static org.dbunit.operation.DatabaseOperation.CLEAN_INSERT;
import static org.dbunit.operation.DatabaseOperation.NONE;

import java.sql.SQLException;

import org.dbunit.DatabaseUnitException;
import org.dbunit.JndiDatabaseTester;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.junit.After;
import org.junit.Before;

public abstract class ArquillianDBUnitTestBase {

	protected abstract String getLookupName();

	protected abstract IDataSet getTestDataSet() throws Exception;

	protected DatabaseOperation getSetupOperation() {
		return CLEAN_INSERT;
	}

	protected DatabaseOperation getTeardownOperation() {
		return NONE;
	}

	protected void setupDatabaseConfig(DatabaseConfig config) throws Exception {
	}

	@Before
	public void beforeTest() throws DatabaseUnitException, SQLException, Exception {
		performDatabaseOperation(getSetupOperation());
	}

	@After
	public void afterTest() throws DatabaseUnitException, SQLException, Exception {
		performDatabaseOperation(getTeardownOperation());
	}

	private void performDatabaseOperation(DatabaseOperation databaseOperation) throws DatabaseUnitException, SQLException, Exception {
		IDataSet testDataSet = getTestDataSet();

		if (NONE.equals(getSetupOperation()) || testDataSet == null) {
			return;
		}

		IDatabaseConnection connection = getConnection();

		try {
			setupDatabaseConfig(connection.getConfig());

			databaseOperation.execute(connection, testDataSet);
		}
		finally {
			connection.close();
		}
	}

	private IDatabaseConnection getConnection() throws Exception {
		return new JndiDatabaseTester(getLookupName()).getConnection();
	}
}


<|start_filename|>src/main/java/org/example/kickoff/business/email/EmailService.java<|end_filename|>
package org.example.kickoff.business.email;

import static org.example.kickoff.business.email.EmailTemplate.EmailTemplatePart.BODY_TITLE;
import static org.example.kickoff.business.email.EmailTemplate.EmailTemplatePart.SUBJECT_CONTENT;
import static org.omnifaces.utils.Lang.isEmpty;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.example.kickoff.business.email.EmailTemplate.EmailTemplatePart;
import org.example.kickoff.business.exception.SystemException;
import org.omnifaces.cdi.settings.ApplicationSetting;

public abstract class EmailService  {

	private static final String LOG_MAIL_FAIL = "Failed to send mail [type=%s, to=%s, replyTo=%s, subject=%s]";
	private static final List<String> ALLOWED_FROM_DOMAINS = Arrays.asList("kickoff.example.org");

	private EmailUser defaultEmailUser;

	@Inject @ApplicationSetting
	private String fromEmail;

	@Inject @ApplicationSetting
	private Boolean disableEmailService;

	@Inject
	private EmailTemplateService emailTemplateService;

	@PostConstruct
	public void init() {
		defaultEmailUser = new EmailUser(fromEmail, "The Java EE Kickoff Team");
	}

	public void sendTemplate(EmailTemplate templateEmail) {
		sendTemplate(templateEmail, new HashMap<>());
	}

	public void sendTemplate(EmailTemplate templateEmail, Map<String, Object> messageParameters) {
		if (disableEmailService) {
			return;
		}

		if (templateEmail.getFromUser() == null) {
			templateEmail.setFromUser(defaultEmailUser);
		}
		else { // Prevent sending email from other domains.
			EmailUser fromUser = templateEmail.getFromUser();
			String email = fromUser.getEmail();

			if (email != null && email.contains("@") && !ALLOWED_FROM_DOMAINS.contains(email.substring(email.indexOf("@") + 1))) {
				templateEmail.setFromUser(new EmailUser(defaultEmailUser.getEmail(), fromUser.getFullName() + " via Java EE Kickoff App"));
				templateEmail.setReplyTo(fromUser.getEmail());
			}
		}

		if (templateEmail.getToUser() == null) {
			templateEmail.setToUser(defaultEmailUser);
		}

		emailTemplateService.addUserParameters("toUser", templateEmail.getToUser(), messageParameters);
		emailTemplateService.addUserParameters("fromUser", templateEmail.getFromUser(), messageParameters);

		try {
			Map<String, String> templateContent = buildTemplateContent(templateEmail, messageParameters);
			sendTemplateMessage(templateEmail, messageParameters, templateContent);
		}
		catch (Exception e) {
			String errorMessage = String.format(LOG_MAIL_FAIL, templateEmail.getType(), templateEmail.getToUser().getEmail(), templateEmail.getReplyTo(), templateEmail.getTemplateParts().get(SUBJECT_CONTENT));
			throw new SystemException(errorMessage, e);
		}
	}

	public abstract void sendTemplateMessage(EmailTemplate templateEmail, Map<String, Object> messageParameters, Map<String, String> templateContent);

	public void sendPlainText(EmailUser to, String subject, String body) {
		sendPlainText(to, defaultEmailUser, subject, body, null);
	}

	public void sendPlainText(EmailUser to, EmailUser from, String subject, String body, String replyTo) {
		if (disableEmailService) {
			return;
		}

		try {
			sendPlainTextMessage(to, from, subject, body, replyTo);
		}
		catch (Exception e) {
			String errorMessage = String.format(LOG_MAIL_FAIL, to, replyTo, subject, body);
			throw new SystemException(errorMessage, e);
		}
	}

	public abstract void sendPlainTextMessage(EmailUser to, EmailUser from, String subject, String body, String replyTo);

	private Map<String, String> buildTemplateContent(EmailTemplate templateEmail, Map<String, Object> messageParameters) {
		Map<EmailTemplatePart, String> templateParts = templateEmail.getTemplateParts();
		Arrays.stream(EmailTemplatePart.values())
			.filter(part -> !templateParts.containsKey(part.getKey()))
			.forEach(part -> templateParts.putIfAbsent(part, emailTemplateService.build(templateEmail, part, messageParameters)));

		if (isEmpty(templateParts.get(BODY_TITLE))) {
			String subjectContent = templateParts.get(SUBJECT_CONTENT);
			templateParts.put(BODY_TITLE, subjectContent);
		}

		Map<String, String> templateContent = new HashMap<>();
		templateEmail.getTemplateParts().keySet().stream()
			.forEach(key -> templateContent.put(key.getKey(), templateEmail.getTemplateParts().get(key)));

		return templateContent;
	}

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/NonDeletableEntityException.java<|end_filename|>
package org.example.kickoff.business.exception;

public class NonDeletableEntityException extends EntityException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/CredentialsException.java<|end_filename|>
package org.example.kickoff.business.exception;

public abstract class CredentialsException extends BusinessException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/view/phaselistener/FacesRequestLoggerPatch.java<|end_filename|>
package org.example.kickoff.view.phaselistener;

import static javax.faces.component.behavior.ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAME;
import static org.omnifaces.cdi.viewscope.ViewScopeManager.isUnloadRequest;
import static org.omnifaces.config.OmniFaces.OMNIFACES_EVENT_PARAM_NAME;
import static org.omnifaces.util.Components.getActionExpressionsAndListeners;
import static org.omnifaces.util.Components.getCurrentActionSource;
import static org.omnifaces.util.FacesLocal.getRequestParameter;
import static org.omnifaces.util.Utils.coalesce;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import org.omnifaces.eventlistener.FacesRequestLogger;

/**
 * Patch FacesRequestLogger to not throw NPE on unload requests. Will be fixed in OmniFaces 3.3.
 */
public class FacesRequestLoggerPatch extends FacesRequestLogger {

	private static final long serialVersionUID = 1L;

	@Override
	protected Map<String, Object> getActionDetails(FacesContext context) {
		UIComponent actionSource = isUnloadRequest(context) ? null : getCurrentActionSource();
		Map<String, Object> actionDetails = new LinkedHashMap<>();
		actionDetails.put("source", actionSource != null ? actionSource.getClientId(context) : null);
		actionDetails.put("event", coalesce(getRequestParameter(context, BEHAVIOR_EVENT_PARAM_NAME), getRequestParameter(context, OMNIFACES_EVENT_PARAM_NAME)));
		actionDetails.put("methods", getActionExpressionsAndListeners(actionSource));
		actionDetails.put("validationFailed", context.isValidationFailed());
		return actionDetails;
	}

}


<|start_filename|>src/main/webapp/resources/scripts/listeners.js<|end_filename|>
/*
 * Define all document event listeners here.
 * 
 * IMPORTANT: use $(document).on("event", "selector", function) syntax so that it's safe against DOM changes on ajax updates.
 * So: do NOT use $("selector").on("event", function) nor $("selector").event(function) syntaxes for this!
 */

/**
 * Remove field's own highlight and message on entering <k:input> value.
 */
$(document).on("click keyup change", ".field :input, .field .ui-inputfield", function() {
	var $input = $(this);
	var $field = $input.closest(".field");
	var $label = $field.children("label");
	var $error = $field.closest(".ERROR");

	var oldValue = $input.data("oldValue");
	var newValue = $input.val();

	if ($error.length && typeof oldValue === "undefined" && !$input.is(":checkbox") && !$input.is("select")) {
		$input.data("oldValue", oldValue = newValue);
	}

	if (oldValue == newValue) {
		return;
	}

	if ($error.length) {
		$error.removeClass("ERROR").find("ul.messages").hide();
		$error.find(".ui-state-error").removeClass("ui-state-error");
	}

	$input.data("oldValue", newValue);
});

/**
 * Close hamburger menu when main section is clicked.
 */
$(document).on("click", "main", function() {
	if (kickoff.isMobile()) {
		$("#hamburger:checked").prop("checked", false);
	}
});

<|start_filename|>src/main/java/org/example/kickoff/business/exception/InvalidGroupException.java<|end_filename|>
package org.example.kickoff.business.exception;

/**
 * Thrown when login username does exist in DB, but user does not have the correct group.
 */
public class InvalidGroupException extends CredentialsException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/config/auth/KickoffRememberMeIdentityStore.java<|end_filename|>
package org.example.kickoff.config.auth;

import static org.example.kickoff.model.LoginToken.TokenType.REMEMBER_ME;
import static org.omnifaces.util.Servlets.getRemoteAddr;

import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.security.enterprise.CallerPrincipal;
import javax.security.enterprise.credential.RememberMeCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.RememberMeIdentityStore;
import javax.servlet.http.HttpServletRequest;

import org.example.kickoff.business.service.LoginTokenService;
import org.example.kickoff.business.service.UserService;

@ApplicationScoped
public class KickoffRememberMeIdentityStore implements RememberMeIdentityStore {

	@Inject
	private HttpServletRequest request;

	@Inject
	private UserService userService;

	@Inject
	private LoginTokenService loginTokenService;

	@Override
	public CredentialValidationResult validate(RememberMeCredential credential) {
		return KickoffIdentityStore.validate(() -> userService.getByLoginToken(credential.getToken(), REMEMBER_ME));
	}

	@Override
	public String generateLoginToken(CallerPrincipal callerPrincipal, Set<String> groups) {
		String ipAddress = getRemoteAddr(request);
		String description = "Remember me session for " + ipAddress + " on " + request.getHeader("User-Agent");
		return loginTokenService.generate(callerPrincipal.getName(), ipAddress, description, REMEMBER_ME);
	}

	@Override
	public void removeLoginToken(String loginToken) {
		loginTokenService.remove(loginToken);
	}

}

<|start_filename|>src/main/java/org/example/kickoff/config/StartupBean.java<|end_filename|>
package org.example.kickoff.config;

import static java.text.MessageFormat.format;
import static java.util.ResourceBundle.getBundle;
import static org.example.kickoff.model.Group.ADMIN;
import static org.omnifaces.util.Faces.getLocale;
import static org.omnifaces.utils.Lang.isEmpty;

import java.util.ResourceBundle;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;
import org.omnifaces.cdi.Startup;
import org.omnifaces.util.Messages;

@Startup
public class StartupBean {

	@Inject
	private UserService userService;

	@PostConstruct
	public void init() {
		setupTestUsers();
		configureMessageResolver();
	}

	private void setupTestUsers() {
		if (!userService.findByEmail("<EMAIL>").isPresent()) {
			User user = new User();
			user.setFirstName("Test");
			user.setLastName("Admin");
			user.setEmail("<EMAIL>");
			userService.register(user, "<PASSWORD>", ADMIN);
		}

		if (!userService.findByEmail("<EMAIL>").isPresent()) {
			User user = new User();
			user.setFirstName("Test");
			user.setLastName("User");
			user.setEmail("<EMAIL>");
			userService.register(user, "<PASSWORD>");
		}
	}

	private void configureMessageResolver() {
		Messages.setResolver(new Messages.Resolver() {
			private static final String BASE_NAME = "org.example.kickoff.i18n.ApplicationBundle";

			@Override
			public String getMessage(String message, Object... params) {
				ResourceBundle bundle = getBundle(BASE_NAME, getLocale());
				if (bundle.containsKey(message)) {
					message = bundle.getString(message);
				}
				return isEmpty(params) ? message : format(message, params);
			}
		});
	}

}

<|start_filename|>src/main/java/org/example/kickoff/model/validator/Email.java<|end_filename|>
package org.example.kickoff.model.validator;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Size;

@Constraint(validatedBy = EmailValidator.class)
@Size(max = 254, message = "{invalid.email}")
@Documented
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface Email {

	String message() default "{invalid.email}";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/InvalidPasswordException.java<|end_filename|>
package org.example.kickoff.business.exception;

/**
 * Thrown when login username does exist in DB, but password does not match.
 */
public class InvalidPasswordException extends CredentialsException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/webapp/resources/scripts/functions.js<|end_filename|>
/*
 * Define all custom functions and global initialization here. 
 * 
 * IMPORTANT: use a custom namespace so they don't pollute global scope!
 */
var kickoff = function(window, document) {


	// Properties -----------------------------------------------------------------------------------------------------

	var self = {};
	var currentMedia;
	var windowUnloading;


	// Public functions -----------------------------------------------------------------------------------------------

	self.isDesktop = function() {
	    return currentMedia == "desktop";
	}

	self.isTablet = function() {
	    return currentMedia == "tablet";
	}

	self.isMobile = function() {
	    return currentMedia == "mobile";
	}

	self.isInProgress = function() {
		return $("html").hasClass("progress");
	}

	self.showMessage = function(message, severity) {
		if (severity == "fatal") {
			message += "<br/>Please <a href='#' onclick='location.reload(true)'>reload</a> and try again.";
		}
		else if (!severity) {
			severity = "info";
		}

		$("#messages").html("<div class='ui-messages-" + severity + " ui-corner-all'>"
			+ "<a href='#' class='ui-messages-close' onclick='$(this).parent().fadeOut();return false;'>"
			+ "<span class='ui-icon ui-icon-close'></span></a><span class='ui-messages-" + severity + "-icon'></span>"
			+ "<ul><li><span class='ui-messages-" + severity + "-summary'>"
			+ message
			+ "</span></li></ul></div>");
	}

	self.pfOnsuccess = function(callback) {
		var args = arguments.callee.caller.arguments;

		if (args && args[2] && !args[2].validationFailed) {
			callback($(document.getElementById(args[0].pfSettings.source)));
		}
	}


	// Private functions ----------------------------------------------------------------------------------------------
	
	var hideGlobalMessages = function() {
		$("#messages > div").slideUp();
	};
	
	var autoHideGlobalMessages = function() {
		var $messages = $("#messages > div");

		if (!$messages.length || $messages.hasClass("ui-messages-fatal")) { // Don't autohide fatal messages.
			return;
		}

		var wordCount = $messages.text().split(/\W/).length;
		var readingTimeMillis = 3000 + (wordCount * 200); // First 3 secs "warming up" time (i.e. give human eye chance to find/focus/understand message dialog) and then 200ms per word.

		setTimeout(hideGlobalMessages, readingTimeMillis);
		$("#messages").on("click", hideGlobalMessages);
	};

	var getSubmittingForm = function(source) {
		var sourceClientId = (typeof source === "object") ? source.id : source;
		var $form = $(document.getElementById(sourceClientId)).closest("form");

		while (!$form.length && sourceClientId.indexOf(":") > 0) { // May happen when source disappears after a conditional rendering.
			sourceClientId = sourceClientId.substring(0, sourceClientId.lastIndexOf(":"));
			$form = $(document.getElementById(sourceClientId)).closest("form");
		}

		return $form;
	};


	// Global initialization ------------------------------------------------------------------------------------------

	/**
	 * Set default jQuery UI animation speed to 300ms.
	 */
	$.fx.speeds._default = 300;

	/**
	 * Setup window events.
	 * - On window load, autohide global (flash) messa.ges.
	 * - On window load, resize and orientationchange, trigger custom window event 'mediachange' when CSS media changes, and trigger custom window event 'render'.
	 * - On window unload, mark state so that any ajax errors caused by window unload won't trigger server error.
	 */
	$(window).on("load", function() {
		autoHideGlobalMessages();
	}).on("load resize orientationchange", function() {
		var media = window.getComputedStyle ? window.getComputedStyle(document.body, ":after").content.replace(/"/g, "") : "desktop";

		if (media != currentMedia) {
			currentMedia = media;
			$(window).trigger("mediachange", media);
		}

		$(window).trigger("render", media);
	}).on("unload", function() {
		windowUnloading = true;
	});

	/**
	 * Setup unload events. 
	 * - On change of non-stateless inputs in non-stateless forms, mark unsaved changes.
	 * - On submit of any PrimeFaces ajax form in the document, unmark unsaved changes.
	 * - On submit of any synchronous form in the document, unmark unsaved changes.
	 * - On window beforeunload, if there are any unsaved changes, show the unload message defined as <body data-unloadmessage>.
	 * This explicitly uses window.onbeforeunload instead of $(window).on("beforeunload") or window.addEventListener("beforeunload"), 
	 * so OmmiFaces ViewScoped unload script will continue to work properly.
	 */
	$(document).on("change", "form:not(.stateless) :input:not(.stateless):not(.ui-column-filter)", function() {
		$("body").data("unsavedchanges", true);
	}).on("pfAjaxComplete", function() {
		$("body").data("unsavedchanges", false);
	}); OmniFaces.Util.addSubmitListener(function() {
		$("body").data("unsavedchanges", false);
	}); window.onbeforeunload = function() {
		return $("body").data("unsavedchanges") ? $("body").data("unloadmessage") : null;
	};

	/**
	 * Setup PrimeFaces ajax events.
	 * - On ajax start, disable PF built in focus listener which refocuses last active element (as we already have our own focus logic via o:highlight).
	 * - On ajax send, trigger progress overlay and custom document event 'startProgress'.
	 * - On ajax success, abort all PF ajax requests currently in queue (double submit prevention when e.g. submit+blur are invoked simultaneously).
	 * - On ajax error, show fatal server error message (only if windows is not unloading).
	 * - On ajax complete, toggle 'validationFailed' class on parent form if necessary, trigger custom document event 'stopProgress', hide overlay and autohide global messages.
	 */
	$(document).on("pfAjaxStart", function(event) {
		PrimeFaces.customFocus = true;
	}).on("pfAjaxSend", function(event, xhr, options) {
		$("html").addClass("progress");
		$(document).trigger("startProgress");
	}).on("pfAjaxSuccess", function(event, xhr, options) {
		PrimeFaces.ajax.Queue.abortAll();
	}).on("pfAjaxError", function(event, xhr, options) {
		if (!windowUnloading) {
			self.showMessage("The server did not respond as expected!", "fatal");
		}
	}).on("pfAjaxComplete", function(event, xhr, options) {
		$("form.validationFailed").removeClass("validationFailed");

		if (xhr && xhr.pfArgs && options && options.source) {
			getSubmittingForm(options.source).toggleClass("validationFailed", !!xhr.pfArgs.validationFailed);
		}

		if (self.isInProgress()) {
			$(document).trigger("stopProgress");
			$("html").removeClass("progress");
		}

		autoHideGlobalMessages();
	});

	return self;

}(window, document);

<|start_filename|>src/main/java/org/example/kickoff/business/exception/InvalidUsernameException.java<|end_filename|>
package org.example.kickoff.business.exception;

/**
 * Thrown when login username does not exist in DB.
 */
public class InvalidUsernameException extends CredentialsException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/view/viewhandler/LocaleAwareViewHandler.java<|end_filename|>
package org.example.kickoff.view.viewhandler;

import static org.omnifaces.util.Beans.getReference;
import static org.omnifaces.util.FacesLocal.getRequestContextPath;

import javax.faces.application.ViewHandler;
import javax.faces.application.ViewHandlerWrapper;
import javax.faces.context.FacesContext;

import org.example.kickoff.view.ActiveLocale;

public class LocaleAwareViewHandler extends ViewHandlerWrapper {

	private ViewHandler wrapped;

	public LocaleAwareViewHandler(ViewHandler wrapped) {
		this.wrapped = wrapped;
	}

	@Override
	public String getActionURL(FacesContext context, String viewId) {
		String contextPath = getRequestContextPath(context);
		String localePath = getReference(ActiveLocale.class).getPath();
		String uri = super.getActionURL(context, viewId).substring(contextPath.length());
		return contextPath + localePath + uri;
	}

	@Override
	public ViewHandler getWrapped() {
		return wrapped;
	}
}


<|start_filename|>src/main/java/org/example/kickoff/view/auth/SignupBacking.java<|end_filename|>
package org.example.kickoff.view.auth;

import static javax.security.enterprise.authentication.mechanism.http.AuthenticationParameters.withParams;

import java.io.IOException;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.security.enterprise.credential.CallerOnlyCredential;

@Named
@RequestScoped
public class SignupBacking extends AuthBacking {

	public void signup() throws IOException {
		userService.register(user, password);
		authenticate(withParams().credential(new CallerOnlyCredential(user.getEmail())));
	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/user/ProfileBacking.java<|end_filename|>
package org.example.kickoff.view.user;

import static org.omnifaces.util.Messages.addGlobalInfo;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.constraints.NotNull;

import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;
import org.example.kickoff.model.validator.Password;
import org.example.kickoff.view.ActiveUser;

@Named
@RequestScoped
public class ProfileBacking {

	private User user;
	private @NotNull @Password String currentPassword;
	private @NotNull @Password String newPassword;

	@Inject
	private ActiveUser activeUser;

	@Inject
	private UserService userService;

	@PostConstruct
	public void init() {
		user = activeUser.get();
	}

	public void save() {
		userService.update(user);
		addGlobalInfo("user_profile.message.info.account_updated");
	}

	public void changePassword() {
		userService.updatePassword(user, newPassword);
		addGlobalInfo("user_profile.message.info.password_changed");
	}

	public User getUser() {
		return user;
	}

	public String getCurrentPassword() {
		return currentPassword;
	}

	public void setCurrentPassword(String currentPassword) {
		this.currentPassword = currentPassword;
	}

	public String getNewPassword() {
		return newPassword;
	}

	public void setNewPassword(String newPassword) {
		this.newPassword = newPassword;
	}

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/DuplicateEntityException.java<|end_filename|>
package org.example.kickoff.business.exception;

/**
 * Thrown when an unique constraint is violated.
 */
public class DuplicateEntityException extends EntityException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/business/email/EmailTemplateService.java<|end_filename|>
package org.example.kickoff.business.email;

import java.util.Map;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.example.kickoff.business.email.EmailTemplate.EmailTemplatePart;
import org.omnifaces.utils.text.NameBasedMessageFormat;

@Stateless
public class EmailTemplateService {

	@Inject
	@Emails
	private Map<String, String> emails;

	public String build(EmailTemplate templateEmail, EmailTemplatePart templatePart, Map<String, Object> messageParameters) {
		String pattern = templatePart.isHtml() 	? emails.get(templateEmail.getType() + "_" + templatePart.getKey() + "_HTML")
												: emails.get(templateEmail.getType() + "_" + templatePart.getKey() + "_text");

		// Defaults
		if (pattern == null) {
			pattern = templatePart.isHtml()	? emails.get("default_" + templatePart.getKey() + "_HTML")
											: emails.get("default_" + templatePart.getKey() + "_text");
		}

		if (pattern != null) {
			String content = NameBasedMessageFormat.format(pattern, messageParameters).trim();

			if (templatePart.isHtml()) {
				content = content.replaceAll("(\r\n|\n){2,}", "<br/><br/>");
			}

			return content;
		}

		return null;
	}

	public Map<String, Object> addUserParameters(String prefix, EmailUser user, Map<String, Object> messageParameters) {
		messageParameters.putIfAbsent(prefix + ".id", user.getId());
		messageParameters.putIfAbsent(prefix + ".email", user.getEmail());
		messageParameters.putIfAbsent(prefix + ".fullName", user.getFullName());

		String linkPattern = String.format("<span class=\"profile\">%s</span>", String.format("{%s.fullName}", prefix));
		messageParameters.putIfAbsent(prefix, NameBasedMessageFormat.format(linkPattern, messageParameters));

		return messageParameters;
	}

}

<|start_filename|>src/main/java/org/example/kickoff/business/email/KickoffEmailService.java<|end_filename|>
package org.example.kickoff.business.email;

import java.util.Map;

import javax.ejb.Stateless;

@Stateless
public class KickoffEmailService extends EmailService {

	@Override
	public void sendTemplateMessage(EmailTemplate templateEmail, Map<String, Object> messageParameters, Map<String, String> templateContent) {
		// TODO: implement.
		System.out.println("KickoffEmailService.sendTemplateMessage()");
		System.out.println("Call to action URL: " + templateEmail.getCallToActionURL());
		System.out.println("Message parameters: " + messageParameters);
		System.out.println("Template content: " + templateContent);
	}

	@Override
	public void sendPlainTextMessage(EmailUser to, EmailUser from, String subject, String body, String replyTo) {
		// TODO: implement.
		System.out.println("KickoffEmailService.sendPlainTextMessage()");
		System.out.println("To: " + to);
		System.out.println("From: " + from);
		System.out.println("Subject: " + subject);
		System.out.println("Body: " + body);
		System.out.println("Reply to: " + replyTo);
	}

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/EntityException.java<|end_filename|>
package org.example.kickoff.business.exception;

public abstract class EntityException extends BusinessException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/view/composite/InputLocalDate.java<|end_filename|>
package org.example.kickoff.view.composite;

import static java.lang.Boolean.TRUE;
import static java.time.LocalDate.now;
import static java.util.stream.Collectors.toList;
import static java.util.stream.IntStream.iterate;
import static java.util.stream.IntStream.range;
import static org.omnifaces.util.Ajax.update;
import static org.omnifaces.util.Components.getAttribute;
import static org.omnifaces.utils.Lang.coalesce;

import java.io.IOException;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import java.time.YearMonth;
import java.util.List;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;

import org.omnifaces.util.State;
import org.primefaces.component.selectonemenu.SelectOneMenu;

@FacesComponent("inputLocalDate")
public class InputLocalDate extends UIInput implements NamingContainer {

	// Constants ------------------------------------------------------------------------------------------------------

	private static final int DEFAULT_YEAR_RANGE = 100; // Default to -100y ~ +100y

	// Properties -----------------------------------------------------------------------------------------------------

	private SelectOneMenu day;
	private SelectOneMenu month;
	private SelectOneMenu year;

	// Variables ------------------------------------------------------------------------------------------------------

	private final State state = new State(getStateHelper());

	public InputLocalDate() {
		super.setRequired(getAttribute(this, "required") == TRUE);
	}

	// Actions --------------------------------------------------------------------------------------------------------

	@Override
	public String getFamily() {
		return UINamingContainer.COMPONENT_FAMILY;
	}

	/**
	 * Invoked when JSF renders the input field.
	 */
	@Override
	public void encodeBegin(FacesContext context) throws IOException {
		LocalDate now = now();
		LocalDate min = coalesce(getAttribute(this, "min"), now.minusYears(DEFAULT_YEAR_RANGE));
		LocalDate max = coalesce(getAttribute(this, "max"), now.plusYears(DEFAULT_YEAR_RANGE));
		LocalDate value = (LocalDate) getValue();

		day.setValue(value != null ? value.getDayOfMonth() : null);
		month.setValue(value != null ? value.getMonthValue() : null);
		year.setValue(value != null ? value.getYear() : null);

		setYears(iterate(max.getYear(), i -> i - 1).limit(max.getYear() - min.getYear()).boxed().collect(toList()));
		setMonthsBasedOnCurrentYear(max);
		setDaysBasedOnCurrentMonth(max);

		super.encodeBegin(context);
	}

	private int setDaysBasedOnCurrentMonth(LocalDate max) {
		int maxDays = 31;

		if (max != null) {
			Integer currentMonth = (Integer) month.getValue();

			if (currentMonth != null) {
				Integer currentYear = (Integer) year.getValue();

				if (currentYear != null) {

					if (YearMonth.of(currentYear, currentMonth).equals(YearMonth.from(max))) {
						maxDays = max.getDayOfMonth();
					}
					else {
						maxDays = Month.of(currentMonth).length(Year.isLeap(currentYear));
					}
				}
			}
		}

		setDays(range(1, maxDays + 1).boxed().collect(toList()));
		return maxDays;
	}

	private int setMonthsBasedOnCurrentYear(LocalDate max) {
		int maxMonths = Month.values().length;

		if (max != null) {
			Integer currentYear = (Integer) year.getValue();

			if (currentYear != null && currentYear == max.getYear()) {
				maxMonths = max.getMonthValue();
			}
		}

		setMonths(range(1, maxMonths + 1).boxed().collect(toList()));
		return maxMonths;
	}

	/**
	 * Invoked when month dropdown is changed.
	 */
	public void updateDaysIfNecessary() {
		int oldMaxDays = getDays().size();
		int newMaxDays = setDaysBasedOnCurrentMonth(getAttribute(this, "max"));

		if (oldMaxDays != newMaxDays) {
			Integer currentDay = (Integer) day.getValue();

			if (currentDay != null && currentDay > newMaxDays) {
				day.setValue(newMaxDays);
			}

			update(day.getClientId());
		}
	}

	/**
	 * Invoked when year dropdown is changed.
	 */
	public void updateMonthsIfNecessary() {
		int oldMaxMonths = getMonths().size();
		int newMaxMonths = setMonthsBasedOnCurrentYear(getAttribute(this, "max"));

		if (oldMaxMonths != newMaxMonths) {
			Integer currentMonth = (Integer) month.getValue();

			if (currentMonth != null && currentMonth > newMaxMonths) {
				month.setValue(newMaxMonths);
			}

			update(month.getClientId());
		}

		updateDaysIfNecessary();
	}

	/**
	 * Invoked when form is submitted.
	 */
	@Override
	public Object getSubmittedValue() {
		return year.getSubmittedValue() + "-" + month.getSubmittedValue() + "-" + day.getSubmittedValue();
	}

	@Override
	protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException {
		try {
			String[] yearMonthAndDay = ((String) submittedValue).split("-");
			return LocalDate.of(Integer.valueOf(yearMonthAndDay[0]), Integer.valueOf(yearMonthAndDay[1]), Integer.valueOf(yearMonthAndDay[2]));
		}
		catch (Exception e) {
			return null;
		}
	}

	@Override
	public void setValid(boolean valid) {
		day.setValid(valid);
		month.setValid(valid);
		year.setValid(valid);
		super.setValid(valid);
	}

	// Getters/setters ------------------------------------------------------------------------------------------------

	public SelectOneMenu getDay() {
		return day;
	}

	public void setDay(SelectOneMenu day) {
		this.day = day;
	}

	public List<Integer> getDays() {
		return state.get("days");
	}

	public void setDays(List<Integer> days) {
		state.put("days", days);
	}

	public SelectOneMenu getMonth() {
		return month;
	}

	public void setMonth(SelectOneMenu month) {
		this.month = month;
	}

	public List<Integer> getMonths() {
		return state.get("months");
	}

	public void setMonths(List<Integer> months) {
		state.put("months", months);
	}

	public SelectOneMenu getYear() {
		return year;
	}

	public void setYear(SelectOneMenu year) {
		this.year = year;
	}

	public List<Integer> getYears() {
		return state.get("years");
	}

	public void setYears(List<Integer> years) {
		state.put("years", years);
	}

}

<|start_filename|>src/main/webapp/resources/styles/primefaces.css<|end_filename|>
/*
 * Define here ONLY .ui-xxx classes from PrimeFaces/jQueryUI.
 */

/* General ---------------------------------------------------------------------------------------------------------- */

.ui-widget.ui-state-default {
	border: 1px solid #{lightGray};
}

.ui-widget.ui-state-hover {
	border-color: #{semiGray};
}

.ui-widget.ui-state-highlight,
.ui-widget.ui-state-focus {
	border-color: #{black};
}

.ui-widget.ui-state-error {
	border-color: #{red};
}

.ui-corner-all {
	border-radius: 3px;
}

.ui-icon,
.ui-paginator .ui-paginator-first,
.ui-paginator .ui-paginator-prev,
.ui-paginator .ui-paginator-next,
.ui-paginator .ui-paginator-last {
	display: inline-block;
    font: normal normal normal 14px/1 FontAwesome;
    font-size: 1.2em;
    text-indent: 0;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.ui-shadow {
	box-shadow: 0 1px 3px #{gray};
}


/* Inputs ----------------------------------------------------------------------------------------------------------- */

.ui-inputfield {
	position: relative;
	font-size: 1em;
	color: #{black};
}

.ui-inputfield,
.ui-spinner,
.ui-selectonemenu,
.ui-autocomplete {
	box-sizing: border-box;
	width: 100%;
	height: 40px;
	padding: .5em;
	background: white;
}

.ui-autocomplete-panel,
.ui-selectonemenu-panel {
	z-index: 9997 !important;	
}

.ui-selectonemenu-panel .ui-selectonemenu-item.ui-state-disabled,
.ui-autocomplete-panel .ui-autocomplete-item.ui-state-disabled {
	display: none;
}

.ui-selectonemenu-panel .ui-selectonemenu-item.ui-state-hover,
.ui-selectonemenu-panel .ui-selectonemenu-item.ui-state-highlight,
.ui-autocomplete-panel .ui-autocomplete-item.ui-state-hover,
.ui-autocomplete-panel .ui-autocomplete-item.ui-state-highlight {
	background: #{brightGray};
}

.ui-spinner-button.ui-state-default {
	height: 20px;
	width: 20px;
	border-radius: 20px;
	line-height: 20px;
	padding: 0;
	background: white;
	border: 2px solid #{gray};
	color: #{gray};
}

.ui-spinner-button.ui-state-focus,
.ui-spinner-button.ui-state-hover {
	border-color: #{black};
	color: #{black};
}

.ui-spinner-button.ui-state-default .ui-button-text {
	display: inline;
	padding: 0;
}

.ui-spinner-up.ui-state-default {
	top: 16px;
	right: 16px;
}

.ui-spinner-down.ui-state-default {
	top: 16px;
	right: 54px;
}

.ui-spinner .ui-icon {
	position: static;
	margin: 0;
	vertical-align: middle;
}

.ui-spinner-up .ui-icon:after {
	content: "\f067"; /* fa-plus */
}

.ui-spinner-down .ui-icon:after {
	content: "\f068"; /* fa-minus */
}

.ui-chkbox {
	float: left;
}

.ui-chkbox .ui-chkbox-box {
	box-sizing: border-box;
	width: 25px;
	height: 25px;
	padding: 2px;
	line-height: 25px;
	text-align: center;
}

.ui-chkbox .ui-icon-check:after {
	content: "\f00c"; /* fa-check */
	color: #{red};
}


/* Buttons ---------------------------------------------------------------------------------------------------------- */

.ui-button.ui-state-default {
	height: 40px;
	margin: 0 .5em 0 0;
	padding: 0 1em;
	text-transform: uppercase;
	color: white;
	background: #{green};
}

.ui-button.ui-state-hover,
.ui-button.ui-state-focus {
	box-shadow: 0 3px 6px 0 #{gray};
}

.ui-button.secondary.ui-state-default {
	background: #{blue};
}

.ui-button.tertiary.ui-state-default {
	background: #{gray};
}

.ui-splitbutton-menubutton .ui-icon:after {
	content: "\f107"; /* fa-angle-down */
}


/* Menus ------------------------------------------------------------------------------------------------------------ */

.ui-menu {
	background: white;
}


/* Tables ----------------------------------------------------------------------------------------------------------- */

.ui-datatable table {
	table-layout: auto;
}

.ui-datatable thead th,
.ui-datatable tbody td {
	border: 1px solid #{lightGray};
}
.ui-datatable tr:nth-child(even) {
	background: white;
}

.ui-datatable th.ui-state-active,
.ui-datatable tr:nth-child(odd),
.ui-datatable tr:nth-child(even) td.ui-state-active {
	background: #{brightGray};
}

.ui-datatable th.ui-state-hover,
.ui-datatable tr:nth-child(odd) td.ui-state-active {
	background: #{lightGray};
}

.ui-datatable th {
	font-weight: normal;
	background: white;
}

.ui-datatable th.ui-state-active {
	font-weight: bold;
}

/* k:table actions */
.ui-datatable-actions .ui-datatable-search {
	float: left;
}

.ui-datatable-actions .ui-datatable-export {
	float: right;
}

.ui-datatable-actions .ui-inputfield.filter {
	width: auto;
}

.ui-datatable-actions .ui-button {
	margin: 0 0 0 .5em;
}

.ui-datatable-actions .ui-buttonset {
	margin: 0;
}

/* k:column types */
.ui-datatable td.text {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.ui-datatable td.number,
.ui-datatable td.currency {
	white-space: nowrap;
	text-align: right;
}

.ui-datatable td.boolean {
	white-space: nowrap;
	text-align: center;
}

.ui-datatable td.actions {
	white-space: nowrap;
	text-align: left;
}

.ui-columntoggler {
	background: white;
}

.ui-columntoggler .ui-columntoggler-close {
	display: none;
}

.ui-columntoggler .ui-columntoggler-items {
	padding: .2em;
}

.ui-columntoggler .ui-columntoggler-item .ui-chkbox {
	position: static;
	margin-top: 0;
}


/* Paginator -------------------------------------------------------------------------------------------------------- */

.ui-paginator .ui-icon {
	display: none;
}

.ui-paginator .ui-paginator-first:after {
	content: "\f100"; /* fa-angle-double-left */
}

.ui-paginator .ui-paginator-prev:after {
	content: "\f104"; /* fa-angle-left */
}

.ui-paginator .ui-paginator-next:after {
	content: "\f105"; /* fa-angle-right */
}

.ui-paginator .ui-paginator-last:after {
	content: "\f101"; /* fa-angle-double-right */
}

.ui-paginator .ui-state-disabled {
	color: #{gray};
}


<|start_filename|>src/main/java/org/example/kickoff/business/exception/EmailNotVerifiedException.java<|end_filename|>
package org.example.kickoff.business.exception;

public class EmailNotVerifiedException extends CredentialsException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/java/org/example/kickoff/view/auth/LogoutBacking.java<|end_filename|>
package org.example.kickoff.view.auth;

import static org.omnifaces.util.Faces.invalidateSession;
import static org.omnifaces.util.Faces.redirect;
import static org.omnifaces.util.Messages.addFlashGlobalWarn;

import java.io.IOException;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.servlet.ServletException;

import org.omnifaces.util.Faces;

@Named
@RequestScoped
public class LogoutBacking {

	public void logout() throws ServletException, IOException {
		Faces.logout();
		invalidateSession();
		addFlashGlobalWarn("auth.message.warn.logged_out");
		redirect("");
	}

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/BusinessException.java<|end_filename|>
package org.example.kickoff.business.exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback = true)
public abstract class BusinessException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public BusinessException() {
		super();
	}

	public BusinessException(String message) {
		super(message);
	}

	public BusinessException(Throwable cause) {
		super(cause);
	}

	public BusinessException(String message, Throwable cause) {
		super(message, cause);
	}

}

<|start_filename|>src/main/webapp/resources/styles/colors.css<|end_filename|>
/* Grays ------------------------------------------------------------------------------------------------------------ */

.color-black {
	color: #{applicationScope['black']='#181818'};
}
.color-black-light {
	color: #{applicationScope['lightBlack']='#383838'};
}
.color-gray-dark {
	color: #{applicationScope['darkGray']='#787878'};
}
.color-gray-semi {
	color: #{applicationScope['semiGray']='#989898'};
}
.color-gray {
	color: #{applicationScope['gray']='#B8B8B8'};
}
.color-gray-light {
	color: #{applicationScope['lightGray']='#E8E8E8'};
}
.color-gray-bright {
	color: #{applicationScope['brightGray']='#F8F8F8'};
}


/* Primary ---------------------------------------------------------------------------------------------------------- */

.color-red {
	color: #{applicationScope['red']='#D81838'};
}
.color-green {
	color: #{applicationScope['green']='#38B868'};
}
.color-blue {
	color: #{applicationScope['blue']='#3868B8'};
}


/* Secondary -------------------------------------------------------------------------------------------------------- */

.color-orange {
	color: #{applicationScope['orange']='#F89800'};
}
.color-yellow {
	color: #{applicationScope['yellow']='#F8D800'};
}

<|start_filename|>src/main/java/org/example/kickoff/model/producer/LoggerProducer.java<|end_filename|>
package org.example.kickoff.model.producer;

import java.util.logging.Logger;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

@Dependent
public class LoggerProducer {

	@Produces
	public Logger getLogger(InjectionPoint injectionPoint) {
		return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/admin/UsersBacking.java<|end_filename|>
package org.example.kickoff.view.admin;

import static org.omnifaces.util.Messages.addGlobalWarn;

import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;

import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;
import org.omnifaces.cdi.ViewScoped;
import org.omnifaces.optimusfaces.model.PagedDataModel;

@Named
@ViewScoped
public class UsersBacking implements Serializable {

	private static final long serialVersionUID = 1L;

	private PagedDataModel<User> users;

	@Inject
	private UserService userService;

	@PostConstruct
	public void init() {
		users = PagedDataModel.lazy(userService).build();
	}

	public void delete(User user) {
		// userService.delete(user);
		addGlobalWarn("This is just a demo, we won't actually delete users for now.");
	}

	public PagedDataModel<User> getUsers() {
		return users;
	}

}

<|start_filename|>src/main/webapp/resources/styles/layout.css<|end_filename|>
/* 
 * The great z-index overview:
 * 
 * 9999: progress overlay
 * 9998: noscript/nobrowser warnings, menus (nav, pop, tooltip, etc)
 * 9997: p:selectOneMenu panel, p:autoComplete panel, p:calendar panel
 * 9996: fixed header
 */

/*
 * The great margin vs padding guideline:
 *
 * - margins of block elements next each other auto-collapse
 * - margin of inline(-block) elements doesn't auto-collapse 
 * - margin is outside click/touch area
 * - margin doesn't cover background
 */

/*
 * The great px vs em guideline:
 *
 * - use px on position/offset/dimensions (*width, *height, *size, *shadow, *spacing, *radius) of all elements
 * - use px on margin/padding of border-box elements (as of now, only input fields are border-box)
 * - use em on font sizes and line heights which are relative to the px-fixed values of the body 
 * - use em on the remainder, which is usually only margin/padding of (inline-)block elements
 * - never use 0em, but use 0 (CSS compressors like YUI compressor may otherwise bug on it)
 * 
 * Base font size is 16px and base line height is 26px. Use em to specify relative sizes. Online calculator: pxtoem.com
 * This scales better when changing base font size in screen width breakpoints.
 * 11px = .688em
 * 13px = .813em
 * 14px = .875em
 * 16px = 1em
 * 20px = 1.25em
 * 24px = 1.5em
 * 32px = 2em
 * 40px = 2.5em
 * 42px = 2.625em
 * If line height needs to scale too (e.g. icons), use exactly the same em value for line-height as used for font-size.
 */

/*
 * Colors except "white" and "transparent" are defined as application attributes in colors.css.
 */

/*
 * Do NOT define here .ui-xxx classes from PrimeFaces/jQueryUI, use primefaces.css for that.
 */


/* Fonts ------------------------------------------------------------------------------------------------------------ */

body,
body input,
body select,
body textarea,
body button {
	font-family: 'Roboto Condensed', sans-serif;
	font-weight: 400;
}

body code,
body input[type=password] {
	font-family: 'Roboto Mono', monospace;
	font-size: .875em;
	font-weight: 400;
}

body b,
body dt,
body strong {
	font-weight: 700;
}

body small {
	font-size: .875em;
	font-weight: 300;
}

body i.fa {
	width: 1.25em;
	height: 1.25em;
	font-size: 1.25em;
	line-height: 1.25em;
	text-align: center;
}

body img {
	line-height: 1em;
	vertical-align: inherit;
}


/* Layout ----------------------------------------------------------------------------------------------------------- */

html {
	height: 100%;
	overflow-y: scroll;
	background: #{brightGray};
}

body {
	display: flex;
	flex-direction: column;
	height: 100%;
	min-width: 300px;
	margin: 0;
	color: #{black};
}

body .warningPadding {
	height: 30px;
}

body .warningMessage {
	position: fixed;
	z-index: 9998;
	top: 0;
	left: 0;
	right: 0;
	height: 30px;
	text-align: center;
	color: white;
	background: #{orange};
}

body .warningMessage a {
	color: white;
	text-decoration: underline;
}

body > header {
	position: fixed;
	z-index: 9996;
	top: 0;
	left: 0;
	right: 0;
	height: 5em;
	line-height: 5em;
	padding: 0 1em;
	background: white;
	border-bottom: 1px solid #{darkGray};
}

body > header > div#logo {
	float: left;
}

body > header > div#logo img {
	vertical-align: middle;
}

body > header > input#hamburger,
body > header > input#hamburger + label {
	position: absolute;
	display: none;
	color: #{red};
}

body > header nav {
	float: left;
	margin-left: 1em;
}

body > header nav ul {
	margin: 0;
	padding: 0;
	list-style-type: none;
}

body > header nav li {
	display: inline-block;
}

body > header nav a {
	display: block;
	padding: 0 1em;
	text-align: center;
	vertical-align: middle;
	white-space: nowrap;
}

body > header nav a:focus,
body > header nav a:hover {
	text-decoration: none;
	background: #{brightGray};
}

body > header nav a:active,
body > header nav a.current {
	background: #{lightGray};
}

body > header nav aside {
	position: absolute;
	top: 0;
	right: 0;
}

body > header nav li ul {
	border: 1px solid #{darkGray};
	border-top: 0;
	right: -1px;
}

body > header nav li li {
	width: 100%;
	line-height: 3em;
}

body > header nav li li a {
	padding: 0;
}

body > main {
	flex: 1;
	-ms-flex: 1 0 auto; /* Those must indeed be declared AFTER flex: 1 as those browsers support it but have bugs. */
	-moz-box-flex: 1 0 0%; 
	width: 100%;
	max-width: 1200px;
	margin: 5em auto 1em;
}

body > main header,
body > main article {
	margin: 1em;
}

body > footer {
	padding-bottom: 1em;
	background: #{lightGray};
}

body > footer section {
	display: inline-block;
	margin: 0 1em;
	vertical-align: top;
}

body > footer ul {
	margin: 0;
	padding: 0;
	list-style-type: none;
}

body > footer p {
	margin: 1em;
}


/* General ---------------------------------------------------------------------------------------------------------- */

article, aside, footer, header, main, nav, section {
	display: block;
}

a {
	text-decoration: none;
	color: #{blue};
}

a:hover {
	text-decoration: underline;
}

pre {
	padding: .5em;
	overflow: auto;
	overflow-y: hidden;
	word-wrap: normal;
	background: white;
	line-height: 1em;
}

nav li ul {
	display: none;
	position: absolute;
	left: 0;
	background: white;
	line-height: normal;
}

nav aside li ul {
	left: auto;
	right: 0;
}
nav li:hover ul {
	display: block;
}

.dev {
	margin: 1em 0;
	padding: 0 .5em;
	background: #{yellow};
}

.dev:before {
	content: "DEV";
	margin-right: .5em;
	font-weight: 700;
	color: #{red};
}


/* Forms ------------------------------------------------------------------------------------------------------------ */

html #overlay {
	display: none;
}

html.progress #overlay {
	position: fixed;
	z-index: 9999;
	display: block;
	opacity: 0;
}

html.progress,
html.progress * {
	cursor: wait !important;
}

fieldset {
	max-width: 600px;
	margin: 1em auto;
	padding: .5em 1.5em;
	border: 0;
	border-radius: 6px;
	background: white;
}

fieldset .field {
	position: relative;
	box-sizing: border-box;
	margin: 1em 0;
}

fieldset .field label > span {
	float: right;
	padding-left: .5em;
}

fieldset .field.checkbox label {
	padding-left: .5em;
}

fieldset .field ul.messages {
	margin: 0;
	padding: 0;
	list-style-type: none;
}

fieldset .field.buttons ul.messages {
	margin: .5em 0;
}

fieldset .field ul.messages .info {
	color: #{green};
}

fieldset .field ul.messages .warn {
	color: #{blue};
}

fieldset .field ul.messages .error {
	color: #{red};
}


/* Normal screens (3-column layout) --------------------------------------------------------------------------------- */

@media screen {

	body:after {
		content: "desktop";
		display: none;
	}
	
	body {
		font-size: 16px;
		line-height: 26px;
		transition: all .3s;
	}

}


/* Medium screens (2-column based) ---------------------------------------------------------------------------------- */

@media print {

	body .hideOnPrint {
		display: none !important;
	}

}

@media print, screen and (max-width: 1023px) {

	body:after {
		content: "tablet";
		display: none;
	}
	
	body {
		font-size: 15px;
		line-height: 25px;
		transition: all .3s;
	}

}


/* Small screens (1-column based) ----------------------------------------------------------------------------------- */

@media screen and (max-width: 767px) {

	body:after {
		content: "mobile";
		display: none;
	}
	
	body .hideOnMobile {
		display: none;
	}

	body {
		font-size: 18px;
		line-height: 28px;
		transition: all .3s;
	}

	body > header {
		height: 4em;
		line-height: 4em;
	}
	
	body > header > input#hamburger + label {
		display: block;
		padding: 0 1em;
		cursor: pointer;
		right: 0;
		transition: right .3s;
		background: white;
	}
	
	body > header > input#hamburger:checked + label {
		right: 33%;
	}

	body > header > input#hamburger ~ nav {
		position: fixed;
		z-index: 9998;
		top: 0;
		right: -33%;
		transition: right .3s;
		width: 32%;
		height: 100%;
		overflow-y: auto;
		background: white;
		border-left: 1px solid #{darkGray};
	}

	body > header > input#hamburger:checked ~ nav {
		right: 0;
	}

	body > header nav li,
	body > header nav li ul {
		display: block;
		border: 0;
		line-height: 3em;
	}

	body > header nav li ul {
		position: relative;
		margin-top: -3em;
	}

	body > header nav a {
		padding: 0;
	}

	body > header nav aside {
		position: static;
	}

	fieldset {
		max-width: none;
		margin: 1em -1em;
		padding: .5em 1em;
		border-radius: 0; 
	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/converter/BaseEntitySelectItemsConverter.java<|end_filename|>
package org.example.kickoff.view.converter;

import java.util.Objects;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.FacesConverter;

import org.omnifaces.converter.SelectItemsConverter;
import org.omnifaces.persistence.model.BaseEntity;

@FacesConverter("baseEntitySelectItemsConverter")
public class BaseEntitySelectItemsConverter extends SelectItemsConverter {

	@Override
	public String getAsString(FacesContext context, UIComponent component, Object value) {
		if (value instanceof BaseEntity) {
			Object id = ((BaseEntity<?>) value).getId();
			return Objects.toString(id, "");
		}
		else {
			return super.getAsString(context, component, value);
		}
	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/validator/EmailVerifiedValidator.java<|end_filename|>
package org.example.kickoff.view.validator;

import static org.omnifaces.util.Messages.createError;

import java.util.Optional;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import javax.inject.Inject;

import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;

@FacesValidator("emailVerifiedValidator")
public class EmailVerifiedValidator implements Validator {

	@Inject
	private UserService userService;

	@Override
	public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
		if (value == null) {
			return;
		}

		Optional<User> user = userService.findByEmail((String) value);

		if (user.isPresent() && !user.get().isEmailVerified()) {
			throw new ValidatorException(createError("emailVerifiedValidator"));
		}
	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/converter/UserConverter.java<|end_filename|>
package org.example.kickoff.view.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import javax.inject.Inject;

import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;

@FacesConverter(forClass=User.class)
public class UserConverter implements Converter {

	@Inject
	private UserService userService;

	@Override
	public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
		if (modelValue == null) {
			return "";
		}

		if (modelValue instanceof User) {
			return ((User) modelValue).getId().toString();
		}
		else {
			throw new ConverterException(modelValue + " is not an User");
		}
	}

	@Override
	public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
		if (submittedValue == null) {
			return null;
		}

		try {
			return userService.getById(Long.valueOf(submittedValue));
		}
		catch (NumberFormatException e) {
			throw new ConverterException(submittedValue + " is not a valid User ID", e);
		}
	}

}

<|start_filename|>src/main/java/org/example/kickoff/config/DataSourceStagedPropertiesFileLoader.java<|end_filename|>
package org.example.kickoff.config;

import static java.util.logging.Logger.getLogger;
import static org.omnifaces.utils.properties.PropertiesUtils.loadPropertiesFromClasspath;
import static org.omnifaces.utils.properties.PropertiesUtils.loadXMLPropertiesStagedFromClassPath;

import java.util.Map;
import java.util.logging.Logger;

import org.omnifaces.persistence.datasource.PropertiesFileLoader;

/**
 * Adaptor for the switchable datasource as defined in web.xml to be able to read properties from the
 * right file.
 */
public class DataSourceStagedPropertiesFileLoader implements PropertiesFileLoader {

	private static final Logger logger = getLogger(DataSourceStagedPropertiesFileLoader.class.getName());

	@Override
	public Map<String, String> loadFromFile(String fileName) {

		// Make sure we use the same names as the application settings are using
		Map<String, String> omniSettings = loadPropertiesFromClasspath("META-INF/omni-settings");

		Map<String, String> dataSourceProperties = loadXMLPropertiesStagedFromClassPath(
			fileName,
			omniSettings.getOrDefault("stageSystemPropertyName", "omni.stage"),
			omniSettings.get("defaultStage"));

		logger.info(
			"\n\nAbout to install DataSource. \n" +
			"Classname: " + dataSourceProperties.get("className") + "\n" +
			"URL: " + dataSourceProperties.getOrDefault("url", dataSourceProperties.get("URL") + "\n" +
			"See META-INF/conf/" + fileName + " for details. \n" +
			"\n\n")
		);

		return dataSourceProperties;

	}

}

<|start_filename|>src/main/java/org/example/kickoff/view/filter/LocaleFilter.java<|end_filename|>
package org.example.kickoff.view.filter;

import static java.util.concurrent.TimeUnit.DAYS;
import static org.example.kickoff.view.ActiveLocale.COOKIE_MAX_AGE_IN_DAYS;
import static org.example.kickoff.view.ActiveLocale.COOKIE_NAME;
import static org.omnifaces.util.Servlets.addResponseCookie;
import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.example.kickoff.view.ActiveLocale;
import org.omnifaces.filter.HttpFilter;
import org.omnifaces.util.Servlets;

@WebFilter(filterName = "localeFilter")
public class LocaleFilter extends HttpFilter {

	@Inject
	private ActiveLocale activeLocale;

	@Override
	public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {

		if (Servlets.isFacesResourceRequest(request)) {
			chain.doFilter(request, response);
		}
		else if (activeLocale.isExplicitlyRequested()) {
			if (activeLocale.isChanged() && !isFacesEvent(request)) { // Never set locale cookie on ajax or unload events.
				addResponseCookie(request, response, COOKIE_NAME, activeLocale.getLanguageTag(), "/", (int) DAYS.toSeconds(COOKIE_MAX_AGE_IN_DAYS));
			}

			if (activeLocale.isDefaultLocale()) {
				response.sendRedirect(request.getContextPath() + activeLocale.getUri());
			}
			else {
				request.getRequestDispatcher(activeLocale.getUri()).forward(request, response);
			}
		}
		else if (!activeLocale.isDefaultLocale()) {
			response.sendRedirect(request.getContextPath() + activeLocale.getPath() + activeLocale.getUri());
		}
		else {
			chain.doFilter(request, response);
		}
	}

	private static boolean isFacesEvent(HttpServletRequest request) {
		return request.getParameter("javax.faces.behavior.event") != null || request.getParameter("omnifaces.event") != null;
	}

}

<|start_filename|>src/main/java/org/example/kickoff/config/auth/KickoffCallerPrincipal.java<|end_filename|>
package org.example.kickoff.config.auth;

import javax.security.enterprise.CallerPrincipal;

import org.example.kickoff.model.User;

/**
 * @see KickoffIdentityStore
 * @see ActiveUser
 */
public class KickoffCallerPrincipal extends CallerPrincipal {

	private final User user;

	public KickoffCallerPrincipal(User user) {
		super(user.getEmail());
		this.user = user;
	}

	public User getUser() {
		return user;
	}

}

<|start_filename|>src/main/java/org/example/kickoff/model/validator/EmailValidator.java<|end_filename|>
package org.example.kickoff.model.validator;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class EmailValidator implements ConstraintValidator<Email, String> {

	@Override
	public void initialize(Email constraintAnnotation) {
		//
	}

	@Override
	public boolean isValid(String email, ConstraintValidatorContext context) {
		if (email == null) {
			return true; // Let @NotNull handle this.
		}

		try {
			new InternetAddress(email).validate();
		}
		catch (AddressException e) {
			return false;
		}

		return true;
	}

}

<|start_filename|>src/main/webapp/resources/scripts/primefaces.js<|end_filename|>
/*
 * Define all PrimeFaces patches here.
 */

/**
 * Fix <p:slider> to format number with correct separator character in output value.
 * Original source here: https://github.com/primefaces/primefaces/blob/master/src/main/resources/META-INF/resources/primefaces/slider/slider.js
 */
if (PrimeFaces.widget.Slider) {
	PrimeFaces.widget.Slider = PrimeFaces.widget.Slider.extend({
		onSlide: function(event, ui) {
			this._super(event, ui);
			this.output.text(parseInt(this.output.text()).toLocaleString(PrimeFaces.settings.locale));
		}
	});
}

/**
 * Fix <p:spinner> to add missing ui-widget class to input element.
 * Original source here: https://github.com/primefaces/primefaces/blob/master/src/main/resources/META-INF/resources/primefaces/spinner/spinner.js
 */
if (PrimeFaces.widget.Spinner) {
	PrimeFaces.widget.Spinner = PrimeFaces.widget.Spinner.extend({
		init: function(cfg) {
			this._super(cfg);
			this.input.addClass("ui-widget");
		}
	});
}

/**
 * Fix <p:selectBooleanCheckbox> to actually be hovered/toggled when associated label is hovered/clicked.
 * Original source here: https://github.com/primefaces/primefaces/blob/master/src/main/resources/META-INF/resources/primefaces/forms/forms.js
 */
if (PrimeFaces.widget.SelectBooleanCheckbox) {
	PrimeFaces.widget.SelectBooleanCheckbox = PrimeFaces.widget.SelectBooleanCheckbox.extend({
		init: function(cfg) {
			this._super(cfg);

			if (!this.disabled) {
				var $box = this.box;

				$("label[for='" + this.id + "']").on("mouseenter mouseleave", function(e) {
					$box.toggleClass("ui-state-hover", e.type == "mouseenter");
				}).on("click", function() {
					$box.trigger("click");
				});
			}
		}
	});
}

<|start_filename|>src/main/java/org/example/kickoff/model/Group.java<|end_filename|>
package org.example.kickoff.model;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static org.example.kickoff.model.Role.ACCESS_API;
import static org.example.kickoff.model.Role.EDIT_OWN_PROFILE;
import static org.example.kickoff.model.Role.EDIT_PROFILES;
import static org.example.kickoff.model.Role.VIEW_ADMIN_PAGES;
import static org.example.kickoff.model.Role.VIEW_USER_PAGES;

import java.util.Arrays;
import java.util.List;

public enum Group {

	USER(VIEW_USER_PAGES, EDIT_OWN_PROFILE, ACCESS_API),

	ADMIN(VIEW_ADMIN_PAGES, EDIT_PROFILES);

	private final List<Role> roles;

	private Group(Role... roles) {
		this.roles = unmodifiableList(asList(roles));
	}

	public List<Role> getRoles() {
		return roles;
	}

	public static List<Group> getByRole(Role role) {
		return Arrays.stream(values())
		             .filter(group -> group.getRoles().contains(role))
		             .collect(toList());
	}

}

<|start_filename|>src/main/java/org/example/kickoff/business/exception/EntityAlreadyModifiedException.java<|end_filename|>
package org.example.kickoff.business.exception;

public class EntityAlreadyModifiedException extends EntityException {

	private static final long serialVersionUID = 1L;

}

<|start_filename|>src/main/webapp/resources/scripts/onload.js<|end_filename|>
/*
 * Put all inline scripts here which should be immediately executed during window load.
 * 
 * IMPORTANT: Don't define functions or document event listeners here! Use functions.js and listeners.js for that.
 */

/**
 * Initialize script error logging.
 */
window.onerror = function(message, source, line, column, error) {
	try {
		$.post("/script-error", $.param({
			url: window.location.href,
			message: message,
			source: source,
			line: line,
			column: column,
			error: error ? error.stack : null
		}));
	}
	catch(e) {
		// Ignore.
	}
};