diff --git a/ccm-core/src/main/java/org/libreccm/jpautils/MimeTypeConverter.java b/ccm-core/src/main/java/org/libreccm/jpautils/MimeTypeConverter.java
new file mode 100644
index 000000000..4e6f91c20
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/jpautils/MimeTypeConverter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.jpautils;
+
+
+import javax.activation.MimeType;
+import javax.activation.MimeTypeParseException;
+import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
+
+/**
+ * A converter for converting properties of the type {@link MimeType} to
+ * {@code String}.
+ *
+ * @author Jens Pelzetter
+ */
+@Converter(autoApply = true)
+public class MimeTypeConverter implements AttributeConverter {
+
+ @Override
+ public String convertToDatabaseColumn(final MimeType attribute) {
+ return attribute.toString();
+ }
+
+ @Override
+ public MimeType convertToEntityAttribute(final String dbData) {
+ try {
+ return new MimeType(dbData);
+ } catch (MimeTypeParseException ex) {
+ throw new IllegalArgumentException("Not a valid mime type", ex);
+ }
+ }
+
+
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/messaging/Attachment.java b/ccm-core/src/main/java/org/libreccm/messaging/Attachment.java
new file mode 100644
index 000000000..489d2e887
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/messaging/Attachment.java
@@ -0,0 +1,185 @@
+/*
+ * 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.messaging;
+
+import org.libreccm.jpautils.MimeTypeConverter;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Objects;
+
+import javax.activation.MimeType;
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@Entity
+@Table(name = "attachments")
+public class Attachment implements Serializable {
+
+ private static final long serialVersionUID = 2063934721452863106L;
+
+ @Id
+ @Column(name = "message_id")
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long attachmentId;
+
+ @ManyToOne
+ @JoinColumn(name = "message_id")
+ private Message message;
+
+ @Column(name = "mime_type")
+ @Convert(converter = MimeTypeConverter.class)
+ private MimeType mimeType;
+
+ @Column(name = "title")
+ private String title;
+
+ @Column(name = "description")
+ private String description;
+
+ @Column(name = "attachment_data")
+ @Lob
+ private byte[] data;
+
+ public long getAttachmentId() {
+ return attachmentId;
+ }
+
+ public void setAttachmentId(final long attachmentId) {
+ this.attachmentId = attachmentId;
+ }
+
+ public Message getMessage() {
+ return message;
+ }
+
+ protected void setMessage(final Message message) {
+ this.message = message;
+ }
+
+ public MimeType getMimeType() {
+ return mimeType;
+ }
+
+ public void setMimeType(final MimeType mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(final String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public byte[] getData() {
+ return Arrays.copyOf(data, data.length);
+ }
+
+ public void setData(byte[] data) {
+ this.data = Arrays.copyOf(data, data.length);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash
+ = 67 * hash + (int) (attachmentId ^ (attachmentId >>> 32));
+ hash = 67 * hash + Objects.hashCode(message);
+ hash = 67 * hash + Objects.hashCode(mimeType);
+ hash = 67 * hash + Objects.hashCode(title);
+ hash = 67 * hash + Objects.hashCode(description);
+ hash = 67 * hash + Arrays.hashCode(data);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Attachment other = (Attachment) obj;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+
+ if (attachmentId != other.getAttachmentId()) {
+ return false;
+ }
+
+ if (!Objects.equals(message, other.getMessage())) {
+ return false;
+ }
+
+ if (!Objects.equals(mimeType, other.getMimeType())) {
+ return false;
+ }
+ if (!Objects.equals(title, other.getTitle())) {
+ return false;
+ }
+ if (!Objects.equals(description, other.getDescription())) {
+ return false;
+ }
+ return Arrays.equals(data, other.getData());
+ }
+
+ public boolean canEqual(final Object obj) {
+ return obj instanceof Attachment;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s{ "
+ + "attachmentId = %d, "
+ + "message = %s, "
+ + "mimeType = \"%s\", "
+ + "title = \"%s\""
+ + " }",
+ super.toString(),
+ attachmentId,
+ Objects.toString(message),
+ Objects.toString(mimeType),
+ title);
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/messaging/Message.java b/ccm-core/src/main/java/org/libreccm/messaging/Message.java
new file mode 100644
index 000000000..470c0e40c
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/messaging/Message.java
@@ -0,0 +1,232 @@
+/*
+ * 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.messaging;
+
+import org.libreccm.core.CcmObject;
+import org.libreccm.core.Party;
+import org.libreccm.jpautils.MimeTypeConverter;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+import javax.activation.MimeType;
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+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 = "messages")
+public class Message extends CcmObject implements Serializable {
+
+ private static final long serialVersionUID = -9143137794418932025L;
+
+ @OneToOne
+ @JoinColumn(name = "sender_id")
+ private Party sender;
+
+ @Column(name = "subject")
+ private String subject;
+
+ @Column(name = "body")
+ private String body;
+
+ @Column(name = "body_mime_type")
+ @Convert(converter = MimeTypeConverter.class)
+ private MimeType bodyMimeType;
+
+ @Column(name = "sent")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date sent;
+
+ @ManyToOne
+ @JoinColumn(name = "in_reply_to_id")
+ private Message inReplyTo;
+
+ @OneToMany(mappedBy = "inReplyTo")
+ private List replies;
+
+ @OneToMany(mappedBy = "message")
+ private List attachments;
+
+ public Party getSender() {
+ return sender;
+ }
+
+ protected void setSender(final Party sender) {
+ this.sender = sender;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(final String subject) {
+ this.subject = subject;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(final String body) {
+ this.body = body;
+ }
+
+ public MimeType getBodyMimeType() {
+ return bodyMimeType;
+ }
+
+ public void setBodyMimeType(final MimeType bodyMimeType) {
+ this.bodyMimeType = bodyMimeType;
+ }
+
+ public Date getSent() {
+ return new Date(sent.getTime());
+ }
+
+ public void setSent(final Date sent) {
+ this.sent = new Date(sent.getTime());
+ }
+
+ public Message getInReplyTo() {
+ return inReplyTo;
+ }
+
+ protected void setInReplyTo(final Message inReplyTo) {
+ this.inReplyTo = inReplyTo;
+ }
+
+ public List getReplies() {
+ return Collections.unmodifiableList(replies);
+ }
+
+ protected void setReplies(final List replies) {
+ this.replies = replies;
+ }
+
+ protected void addReply(final Message reply) {
+ replies.add(reply);
+ }
+
+ protected void removeReply(final Message reply) {
+ replies.remove(reply);
+ }
+
+ public List getAttachments() {
+ return Collections.unmodifiableList(attachments);
+ }
+
+ protected void setAttachments(final List attachments) {
+ this.attachments = attachments;
+ }
+
+ protected void addAttachment(final Attachment attachment) {
+ attachments.add(attachment);
+ }
+
+ protected void removeAttachment(final Attachment attachment) {
+ attachments.remove(attachment);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 89 * hash + Objects.hashCode(sender);
+ hash = 89 * hash + Objects.hashCode(subject);
+ hash = 89 * hash + Objects.hashCode(body);
+ hash = 89 * hash + Objects.hashCode(bodyMimeType);
+ hash = 89 * hash + Objects.hashCode(sent);
+ hash = 89 * hash + Objects.hashCode(inReplyTo);
+ hash = 89 * hash + Objects.hashCode(replies);
+ hash = 89 * hash + Objects.hashCode(attachments);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Message other = (Message) obj;
+ if (!other.canEqual(obj)) {
+ return false;
+ }
+
+ if (!Objects.equals(sender, other.getSender())) {
+ return false;
+ }
+ if (!Objects.equals(subject, other.getSubject())) {
+ return false;
+ }
+ if (!Objects.equals(body, other.getBody())) {
+ return false;
+ }
+ if (!Objects.equals(bodyMimeType, other.getBodyMimeType())) {
+ return false;
+ }
+ if (!Objects.equals(sent, other.getSent())) {
+ return false;
+ }
+ if (!Objects.equals(inReplyTo, other.getInReplyTo())) {
+ return false;
+ }
+ if (!Objects.equals(replies, other.getReplies())) {
+ return false;
+ }
+ return Objects.equals(attachments, other.getAttachments());
+ }
+
+ @Override
+ public boolean canEqual(final Object obj) {
+ return obj instanceof Message;
+ }
+
+ @Override
+ public String toString(final String data) {
+ return super.toString(String.format(", sender = %s, "
+ + "subject = \"%s\", "
+ + "bodyMimeType = \"%s\", "
+ + "sent = %tF %Jens Pelzetter
+ */
+@Entity
+@Table(name = "threads")
+public class Thread extends CcmObject implements Serializable {
+
+ private static final long serialVersionUID = -395123286904985770L;
+
+ @OneToOne
+ @JoinColumn(name = "root_id")
+ private Message root;
+
+ public Message getRoot() {
+ return root;
+ }
+
+ protected void setRoot(final Message root) {
+ this.root = root;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 31 * hash + Objects.hashCode(root);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Thread other = (Thread) obj;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+
+ return Objects.equals(this.root, other.getRoot());
+ }
+
+ @Override
+ public boolean canEqual(final Object obj) {
+ return obj instanceof Thread;
+ }
+
+ @Override
+ public String toString(final String data) {
+ return super.toString(String.format(", root = %s%s",
+ Objects.toString(root),
+ data));
+ }
+
+}