diff --git a/ccm-core/src/main/java/org/libreccm/workflow/Task.java b/ccm-core/src/main/java/org/libreccm/workflow/Task.java
new file mode 100644
index 000000000..e97c94c9b
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/workflow/Task.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2015 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.workflow;
+
+import org.libreccm.l10n.LocalizedString;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.persistence.AssociationOverride;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.Lob;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@Entity
+@Table(name = "workflow_tasks")
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+public class Task implements Serializable {
+
+ private static final long serialVersionUID = 8161343036908150426L;
+
+ @Id
+ @Column(name = "task_id")
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long taskId;
+
+ @Embedded
+ @AssociationOverride(
+ name = "values",
+ joinTable = @JoinTable(name = "workflow_task_labels",
+ joinColumns = {
+ @JoinColumn(name = "task_id")}))
+ private LocalizedString label;
+
+ @Embedded
+ @AssociationOverride(
+ name = "values",
+ joinTable = @JoinTable(name = "workflow_tasks_descriptions",
+ joinColumns = {
+ @JoinColumn(name = "task_id")}))
+ private LocalizedString description;
+
+ @Column(name = "active")
+ private boolean active;
+
+ @Column(name = "task_state", length = 512)
+ private String taskState;
+
+ @ManyToOne
+ @JoinColumn(name = "workflow_id")
+ private Workflow workflow;
+
+ @ManyToMany(mappedBy = "dependsOn")
+ private List dependentTasks;
+
+ @ManyToMany
+ @JoinTable(name = "workflow_task_dependencies",
+ joinColumns = {
+ @JoinColumn(name = "depends_on_task_id")},
+ inverseJoinColumns = {
+ @JoinColumn(name = "dependent_task_id")})
+ private List dependsOn;
+
+ @ElementCollection
+ @JoinTable(name = "workflow_task_comments",
+ joinColumns = {
+ @JoinColumn(name = "task_id")})
+ @Column(name = "comment")
+ @Lob
+ private List comments;
+
+ public Task() {
+ super();
+
+ label = new LocalizedString();
+ description = new LocalizedString();
+ dependentTasks = new ArrayList<>();
+ dependsOn = new ArrayList<>();
+ comments = new ArrayList<>();
+ }
+
+ public long getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(final long taskId) {
+ this.taskId = taskId;
+ }
+
+ public LocalizedString getLabel() {
+ return label;
+ }
+
+ public void setLabel(final LocalizedString label) {
+ this.label = label;
+ }
+
+ public LocalizedString getDescription() {
+ return description;
+ }
+
+ public void setDescription(final LocalizedString description) {
+ this.description = description;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public void setActive(final boolean active) {
+ this.active = active;
+ }
+
+ public String getTaskState() {
+ return taskState;
+ }
+
+ public void setTaskState(final String taskState) {
+ this.taskState = taskState;
+ }
+
+ public Workflow getWorkflow() {
+ return workflow;
+ }
+
+ protected void setWorkflow(final Workflow workflow) {
+ this.workflow = workflow;
+ }
+
+ public List getDependentTasks() {
+ return Collections.unmodifiableList(dependentTasks);
+ }
+
+ protected void setDependentTasks(final List dependentTasks) {
+ this.dependentTasks = dependentTasks;
+ }
+
+ protected void addDependentTask(final Task task) {
+ dependentTasks.add(task);
+ }
+
+ protected void removeDependentTask(final Task task) {
+ dependentTasks.remove(task);
+ }
+
+ public List getDependsOn() {
+ return Collections.unmodifiableList(dependsOn);
+ }
+
+ protected void setDependsOn(final List dependsOn) {
+ this.dependsOn = dependsOn;
+ }
+
+ protected void addDependsOn(final Task task) {
+ dependsOn.add(task);
+ }
+
+ protected void removeDependsOn(final Task task) {
+ dependsOn.remove(task);
+ }
+
+ public List getComments() {
+ return Collections.unmodifiableList(comments);
+ }
+
+ protected void setComments(final List comments) {
+ this.comments = comments;
+ }
+
+ public void addComment(final String comment) {
+ comments.add(comment);
+ }
+
+ public void removeComment(final String comment) {
+ comments.remove(comment);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 79 * hash + (int) (taskId ^ (taskId >>> 32));
+ hash = 79 * hash + Objects.hashCode(label);
+ hash = 79 * hash + Objects.hashCode(description);
+ hash = 79 * hash + (active ? 1 : 0);
+ hash = 79 * hash + Objects.hashCode(taskState);
+ hash = 79 * hash + Objects.hashCode(workflow);
+ hash = 79 * hash + Objects.hashCode(dependentTasks);
+ hash = 79 * hash + Objects.hashCode(dependsOn);
+ hash = 79 * hash + Objects.hashCode(comments);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Task other = (Task) obj;
+ if (!other.canEqual(obj)) {
+ return false;
+ }
+
+ if (taskId != other.getTaskId()) {
+ return false;
+ }
+ if (!Objects.equals(label, other.getLabel())) {
+ return false;
+ }
+ if (!Objects.equals(description, other.getDescription())) {
+ return false;
+ }
+ if (active != other.isActive()) {
+ return false;
+ }
+ if (!Objects.equals(taskState, other.getTaskState())) {
+ return false;
+ }
+ if (!Objects.equals(workflow, other.getWorkflow())) {
+ return false;
+ }
+ if (!Objects.equals(dependentTasks, other.getDependentTasks())) {
+ return false;
+ }
+ if (!Objects.equals(dependsOn, other.getDependsOn())) {
+ return false;
+ }
+ return Objects.equals(comments, other.getComments());
+ }
+
+ public boolean canEqual(final Object obj) {
+ return obj instanceof Task;
+ }
+
+ @Override
+ public final String toString() {
+ return toString("");
+ }
+
+ public String toString(final String data) {
+ return String.format("%s{ "
+ + "taskId = %d, "
+ + "label = %s, "
+ + "active = %b, "
+ + "taskState = \"%s\", "
+ + "workflow = %s, "
+ + "dependentTasks = %s, "
+ + "dependsOn = %s%s"
+ + " }",
+ super.toString(),
+ taskId,
+ Objects.toString(label),
+ active,
+ taskState,
+ Objects.toString(workflow),
+ Objects.toString(dependentTasks),
+ Objects.toString(dependsOn),
+ data);
+ }
+}
diff --git a/ccm-core/src/main/java/org/libreccm/workflow/UserTask.java b/ccm-core/src/main/java/org/libreccm/workflow/UserTask.java
new file mode 100644
index 000000000..19132ab08
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/workflow/UserTask.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2015 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.workflow;
+
+import org.libreccm.core.User;
+import org.libreccm.core.UserGroup;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@Entity
+@Table(name = "workflow_user_tasks")
+public class UserTask extends Task implements Serializable {
+
+ private static final long serialVersionUID = 4188064584389893019L;
+
+ @Column(name = "locked")
+ private boolean locked;
+
+ @OneToOne
+ @JoinColumn(name = "locking_user_id")
+ private User lockingUser;
+
+ @Column(name = "start_date")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date startDate;
+
+ @Column(name = "due_date")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date dueDate;
+
+ @Column(name = "duration_minutes")
+ private long durationMinutes;
+
+ @OneToOne
+ @JoinColumn(name = "notification_sender")
+ private User notificationSender;
+
+ @OneToMany
+ @JoinTable(name = "workflow_user_task_assigned_users",
+ joinColumns = {
+ @JoinColumn(name = "user_task_id")},
+ inverseJoinColumns = {
+ @JoinColumn(name = "assigned_user_id")})
+ private List assignedUsers;
+
+ @OneToMany
+ @JoinTable(name = "workflow_user_task_assigned_groups",
+ joinColumns = {
+ @JoinColumn(name = "user_task_id")},
+ inverseJoinColumns = {
+ @JoinColumn(name = "assigned_group_id")})
+ private List assignedGroups;
+
+ public UserTask() {
+ super();
+ assignedUsers = new ArrayList<>();
+ assignedGroups = new ArrayList<>();
+ }
+
+ public boolean isLocked() {
+ return locked;
+ }
+
+ public void setLocked(final boolean locked) {
+ this.locked = locked;
+ }
+
+ public User getLockingUser() {
+ return lockingUser;
+ }
+
+ public void setLockingUser(final User lockingUser) {
+ this.lockingUser = lockingUser;
+ }
+
+ public Date getStartDate() {
+ return new Date(startDate.getTime());
+ }
+
+ public void setStartDate(final Date startDate) {
+ this.startDate = new Date(startDate.getTime());
+ }
+
+ public Date getDueDate() {
+ return new Date(dueDate.getTime());
+ }
+
+ public void setDueDate(final Date dueDate) {
+ this.dueDate = new Date(dueDate.getTime());
+ }
+
+ public long getDurationMinutes() {
+ return durationMinutes;
+ }
+
+ public void setDurationMinutes(final long durationMinutes) {
+ this.durationMinutes = durationMinutes;
+ }
+
+ public User getNotificationSender() {
+ return notificationSender;
+ }
+
+ public void setNotificationSender(final User notificationSender) {
+ this.notificationSender = notificationSender;
+ }
+
+ public List getAssignedUsers() {
+ return Collections.unmodifiableList(assignedUsers);
+ }
+
+ protected void setAssignedUsers(final List assignedUsers) {
+ this.assignedUsers = assignedUsers;
+ }
+
+ protected void addAssignedUser(final User user) {
+ assignedUsers.add(user);
+ }
+
+ protected void removeAssignedUser(final User user) {
+ assignedUsers.remove(user);
+ }
+
+ public List getAssignedGroups() {
+ return Collections.unmodifiableList(assignedGroups);
+ }
+
+ protected void setAssignedGroups(final List assignedGroups) {
+ this.assignedGroups = assignedGroups;
+ }
+
+ protected void addAssignedGroup(final UserGroup group) {
+ assignedGroups.add(group);
+ }
+
+ protected void removeAssignedGroup(final UserGroup group) {
+ assignedGroups.remove(group);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 37 * hash + (locked ? 1 : 0);
+ hash = 37 * hash + Objects.hashCode(lockingUser);
+ hash = 37 * hash + Objects.hashCode(startDate);
+ hash = 37 * hash + Objects.hashCode(dueDate);
+ hash
+ = 37 * hash + (int) (durationMinutes ^ (durationMinutes >>> 32));
+ hash = 37 * hash + Objects.hashCode(notificationSender);
+ hash = 37 * hash + Objects.hashCode(assignedUsers);
+ hash = 37 * hash + Objects.hashCode(assignedGroups);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final UserTask other = (UserTask) obj;
+ if (!other.canEqual(obj)) {
+ return false;
+ }
+
+ if (locked != other.isLocked()) {
+ return false;
+ }
+ if (!Objects.equals(lockingUser, other.getLockingUser())) {
+ return false;
+ }
+ if (!Objects.equals(startDate, other.getStartDate())) {
+ return false;
+ }
+ if (!Objects.equals(dueDate, other.getDueDate())) {
+ return false;
+ }
+ if (durationMinutes != other.getDurationMinutes()) {
+ return false;
+ }
+ if (!Objects.equals(notificationSender, other.getNotificationSender())) {
+ return false;
+ }
+ if (!Objects.equals(assignedUsers, other.getAssignedUsers())) {
+ return false;
+ }
+ return Objects.equals(assignedGroups, other.getAssignedGroups());
+ }
+
+ @Override
+ public String toString(final String data) {
+ return super.toString(String.format(", locked = %b, "
+ + "lockingUser = %s, "
+ + "startDate = %tF %Jens Pelzetter
+ */
+@Entity
+@Table(name = "workflows")
+public class Workflow implements Serializable {
+
+ private static final long serialVersionUID = 4322500264543325829L;
+
+ @Id
+ @Column(name = "workflow_id")
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long workflowId;
+
+ @Embedded
+ @AssociationOverride(
+ name = "values",
+ joinTable = @JoinTable(name = "workflow_names",
+ joinColumns = {
+ @JoinColumn(name = "workflow_id")}))
+ private LocalizedString name;
+
+ @Embedded
+ @AssociationOverride(
+ name = "values",
+ joinTable = @JoinTable(name = "workflow_descriptions",
+ joinColumns = {
+ @JoinColumn(name = "workflow_id")
+ }))
+ private LocalizedString description;
+
+ @OneToMany(mappedBy = "workflow")
+ private List tasks;
+
+ public Workflow() {
+ super();
+
+ name = new LocalizedString();
+ description = new LocalizedString();
+ tasks = new ArrayList<>();
+ }
+
+ public long getWorkflowId() {
+ return workflowId;
+ }
+
+ public void setWorkflowId(final long workflowId) {
+ this.workflowId = workflowId;
+ }
+
+ public LocalizedString getName() {
+ return name;
+ }
+
+ public void setName(final LocalizedString name) {
+ this.name = name;
+ }
+
+ public LocalizedString getDescription() {
+ return description;
+ }
+
+ public void setDescription(final LocalizedString description) {
+ this.description = description;
+ }
+
+ public List getTasks() {
+ return Collections.unmodifiableList(tasks);
+ }
+
+ protected void setTasks(final List tasks) {
+ this.tasks = tasks;
+ }
+
+ protected void addTask(final Task task) {
+ tasks.add(task);
+ }
+
+ protected void removeTask(final Task task) {
+ tasks.remove(task);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 79 * hash + (int) (this.workflowId ^ (this.workflowId >>> 32));
+ hash = 79 * hash + Objects.hashCode(this.name);
+ hash = 79 * hash + Objects.hashCode(this.tasks);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Workflow other = (Workflow) obj;
+ if (!other.canEqual(obj)) {
+ return false;
+ }
+
+ if (this.workflowId != other.getWorkflowId()) {
+ return false;
+ }
+ if (!Objects.equals(this.name, other.getName())) {
+ return false;
+ }
+ return Objects.equals(this.tasks, other.getTasks());
+ }
+
+ public boolean canEqual(final Object obj) {
+ return obj instanceof Workflow;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s{ "
+ + "workflowId = %d, "
+ + "name = \"%s\""
+ + " }",
+ super.toString(),
+ workflowId,
+ name);
+ }
+
+}