/*
* Copyright 2011 Michele Mancioppi [michele.mancioppi@gmail.com]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cave.nice.testMessage.data;
import java.io.Closeable;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.Header;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import cave.nice.testMessage.data.ReportingPolicy.ReportingPolicyType;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Text;
public class DataManager implements Closeable {
private final class AccountByRegistrationDateComparator implements
Comparator<Account> {
@Override
public int compare(Account o1, Account o2) {
return o1.getRegistrationDate().compareTo(o2.getRegistrationDate());
}
}
private static final Logger LOGGER = Logger.getLogger(DataManager.class
.getName());
private static final DateTimeZone TIME_ZONE = DateTimeZone
.forID("Europe/Berlin");
private static final EntityManagerFactory ENTITY_MANAGER_FACTORY = Persistence
.createEntityManagerFactory("transactions-optional");
public static DataManager getInstance() {
return new DataManager();
}
private final EntityManager entityManager;
boolean closed = false;
private DataManager() {
entityManager = ENTITY_MANAGER_FACTORY.createEntityManager();
}
@Override
public void close() {
this.entityManager.close();
closed = true;
}
@Override
protected void finalize() throws Throwable {
try {
if (!closed) {
close();
}
} finally {
super.finalize();
}
}
public Test createTest(InternetAddress accountEMailAddress, Date testDate,
UUID challenge) throws UnknownVerifiedAccountException {
return createTest(getVerifiedAccount(accountEMailAddress), testDate,
challenge);
}
public Test createTest(VerifiedAccount account, Date testDate,
UUID challenge) {
Test newTest = new Test();
newTest.setAccount(account);
newTest.setDate(testDate);
newTest.setChallenge(challenge.toString());
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.persist(newTest);
transaction.commit();
return newTest;
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public UnverifiedAccount createUnverifiedAccount(
InternetAddress emailAddress) {
UnverifiedAccount newAccount = new UnverifiedAccount();
newAccount.setEmailAddress(emailAddress.getAddress());
newAccount.setRegistrationDate(new Date());
newAccount.setChallenge(UUID.randomUUID().toString());
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.persist(newAccount);
transaction.commit();
return newAccount;
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public VerifiedAccount createVerifiedAccount(InternetAddress emailAddress,
List<ReportingPolicy> reportingPolicies) {
VerifiedAccount newAccount = new VerifiedAccount();
newAccount.setEmailAddress(emailAddress.getAddress());
newAccount.setRegistrationDate(new Date());
newAccount.setReportingPolicies(reportingPolicies);
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.persist(newAccount);
transaction.commit();
return newAccount;
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public <T extends Account> T getAccount(InternetAddress emailAddress,
Class<T> clazz) throws UnknownEmailAddressException {
try {
return clazz.cast(entityManager.createQuery(
"select from " + clazz.getName() + " where emailAddress='"
+ emailAddress.toString() + "'").getSingleResult());
} catch (Exception e) {
throw new UnknownEmailAddressException(e);
}
}
public List<Test> getClosedTests(VerifiedAccount account)
throws CannotRetrieveEntitiesException {
return getTests(account, true);
}
public DateTime getCurrentTime() {
return new DateTime(TIME_ZONE);
}
public List<Test> getOpenTests(VerifiedAccount account)
throws CannotRetrieveEntitiesException {
return getTests(account, false);
}
public Test getTest(Key testIdentifier)
throws UnknownTestIdentifierException {
Test test = entityManager.find(Test.class, testIdentifier);
if (test == null) {
throw new UnknownTestIdentifierException();
}
return test;
}
public Test getTestByChallenge(UUID challenge)
throws UknownTestChallengeException {
try {
return (Test) entityManager.createQuery(
"select from " + Test.class.getName()
+ " where challenge = '" + challenge + "'")
.getSingleResult();
} catch (Exception e) {
throw new UknownTestChallengeException(e);
}
}
public Test getTestOfAccount(VerifiedAccount account, UUID testChallenge)
throws UnknownVerifiedAccountException,
UknownTestChallengeException, WrongAccountForTestException {
try {
Test test = getTestByChallenge(testChallenge);
if (!test.getAccount().equals(account)) {
throw new WrongAccountForTestException(
"The test with challenge '"
+ testChallenge
+ "' (id: "
+ test.getIdentifier()
+ ") is not associated with the account with email address '"
+ account.getEmailAddress() + "'");
}
return test;
} catch (NoResultException e) {
throw new UknownTestChallengeException(e);
}
}
public List<Test> getTests(VerifiedAccount account, boolean answered)
throws CannotRetrieveEntitiesException {
try {
@SuppressWarnings("unchecked")
List<Test> openTests = entityManager
.createQuery(
"select from " + Test.class.getName()
+ " where account=?1 and answeredOn is "
+ ((answered) ? "not" : "") + " null")
.setParameter(1, account).getResultList();
Collections.sort(openTests, new Test.TestComparatorByDate());
return openTests;
} catch (Exception e) {
throw new CannotRetrieveEntitiesException(e);
}
}
public UnverifiedAccount getUnverifiedAccount(InternetAddress address)
throws UnknownUnverifiedAccountException {
try {
return getAccount(address, UnverifiedAccount.class);
} catch (UnknownEmailAddressException e) {
throw new UnknownUnverifiedAccountException(e);
}
}
public UnverifiedAccount getUnverifiedAccount(Key accountIdentifier)
throws UnknownVerifiedAccountException {
try {
return entityManager.find(UnverifiedAccount.class,
accountIdentifier);
} catch (IllegalArgumentException e) {
throw new UnknownVerifiedAccountException(e);
}
}
public List<UnverifiedAccount> getUnverifiedAccounts()
throws CannotRetrieveEntitiesException {
return getAccounts(UnverifiedAccount.class);
}
public VerifiedAccount getVerifiedAccount(InternetAddress address)
throws UnknownVerifiedAccountException {
try {
return getAccount(address, VerifiedAccount.class);
} catch (UnknownEmailAddressException e) {
throw new UnknownVerifiedAccountException(e);
}
}
public VerifiedAccount getVerifiedAccount(Key accountIdentifier)
throws UnknownVerifiedAccountException {
try {
return entityManager.find(VerifiedAccount.class, accountIdentifier);
} catch (IllegalArgumentException e) {
throw new UnknownVerifiedAccountException(e);
}
}
public List<VerifiedAccount> getVerifiedAccounts()
throws CannotRetrieveEntitiesException {
return getAccounts(VerifiedAccount.class);
}
private <T extends Account> List<T> getAccounts(Class<T> clazz)
throws CannotRetrieveEntitiesException {
try {
@SuppressWarnings("unchecked")
List<T> accounts = entityManager.createQuery(
"select from " + clazz.getName()).getResultList();
Collections.sort(accounts,
new AccountByRegistrationDateComparator());
return accounts;
} catch (Exception e) {
throw new CannotRetrieveEntitiesException(e);
}
}
public void markTestAsAnswered(Test test, Date answerTime)
throws CannotUpdateEntityException, TestAlreadyAnsweredException {
if (test.isAnswered()) {
throw new TestAlreadyAnsweredException();
}
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
test.setAnsweredOn(answerTime);
transaction.commit();
} catch (Exception e) {
throw new CannotUpdateEntityException(e);
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public void removeAccount(Account account) {
EntityTransaction transaction = entityManager.getTransaction();
try {
/*
* FIXME! Re-enable the line below and remove the second transaction
* as soon as Google rolls-out the Cross-group transactions
*/
// entityManager.remove(unverifiedAccount);
transaction.begin();
/*
entityManager.createQuery(
"delete from " + account.getClass().getName()
+ " where emailAddress='"
+ account.getEmailAddress() + "'").executeUpdate();
*/
entityManager.remove(account);
//
transaction.commit();
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public void removeExpiredUnverifiedAccounts() {
DateTime currentDate = getCurrentTime();
Date threshold = currentDate.minus(UnverifiedAccount.EXPIRATION_PERIOD)
.toDate();
int removedAccounts = entityManager
.createQuery(
"delete " + UnverifiedAccount.class.getName()
+ " where registrationDate < $1")
.setParameter(1, threshold).executeUpdate();
LOGGER.info("Deleted " + removedAccounts
+ " unverified accounts past validity date");
}
public void removeTest(Test... tests) {
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
for (Test test : tests) {
entityManager.remove(test);
}
transaction.commit();
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public UnprocessedEMail storeUnprocessedEMail(MimeMessage message)
throws IOException {
UnprocessedEMail unprocessedEMail = new UnprocessedEMail();
try {
StringWriter sw = new StringWriter();
@SuppressWarnings("rawtypes")
Enumeration headers = message.getAllHeaders();
while (headers.hasMoreElements()) {
Header header = (Header) headers.nextElement();
sw.append(header.getName());
sw.append('=');
sw.append(header.getValue());
sw.append('\r');
}
unprocessedEMail.setHeaders(new Text(sw.toString()));
unprocessedEMail.setSubject(new Text(message.getSubject()));
unprocessedEMail.setContent(new Text(IOUtils.toString(message
.getInputStream())));
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.persist(unprocessedEMail);
transaction.commit();
return unprocessedEMail;
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Cannot store unprocessed message: "
+ message, e);
throw new IOException("Cannot store unprocessed message: "
+ message, e);
}
}
public VerifiedAccount verifyAccount(InternetAddress emailAddress,
UUID challenge) throws VerificationChallengeNotMetException {
UnverifiedAccount unverifiedAccount = null;
try {
unverifiedAccount = (UnverifiedAccount) entityManager.createQuery(
"select from " + UnverifiedAccount.class.getName()
+ " where emailAddress='"
+ emailAddress.getAddress() + "' and challenge='"
+ challenge + "'").getSingleResult();
} catch (NoResultException e) {
throw new VerificationChallengeNotMetException();
}
VerifiedAccount newAccount = new VerifiedAccount();
newAccount.setEmailAddress(unverifiedAccount.getEmailAddress());
newAccount.setRegistrationDate(unverifiedAccount.getRegistrationDate());
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
/*
* FIXME! Re-enable the line below and remove the second transaction
* as soon as Google rolls-out the Cross-group transactions
*/
// entityManager.remove(unverifiedAccount);
entityManager.persist(newAccount);
transaction.commit();
transaction.begin();
entityManager.createQuery(
"delete from " + UnverifiedAccount.class.getName()
+ " where emailAddress='"
+ unverifiedAccount.getEmailAddress() + "'")
.executeUpdate();
transaction.commit();
return newAccount;
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public ReportingPolicy addNewReportingPolicy(VerifiedAccount account,
ReportingPolicyType type, Integer notifyAfterMinutes,
Boolean notifyIfNoOpenTests)
throws DuplicatedReportingPolicyException {
for (ReportingPolicy reportingPolicy : account.getReportingPolicies()) {
if (reportingPolicy.getType().equals(type)) {
/*
* TODO Error message
*/
throw new DuplicatedReportingPolicyException();
}
}
ReportingPolicy reportingPolicy = new ReportingPolicy();
reportingPolicy.setType(type);
reportingPolicy.setNotifyAfterMinutes(notifyAfterMinutes);
reportingPolicy.setNotifyIfNoOpenTests(notifyIfNoOpenTests);
reportingPolicy.setAccount(account);
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
entityManager.persist(reportingPolicy);
account.getReportingPolicies().add(reportingPolicy);
transaction.commit();
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
return reportingPolicy;
}
public void removeReportingPolicy(VerifiedAccount account,
ReportingPolicy reportingPolicy) {
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
account.getReportingPolicies().remove(reportingPolicy);
entityManager.remove(reportingPolicy);
transaction.commit();
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
}