package edu.gatech.oad.rocket.findmythings.server.db;
import com.googlecode.objectify.*;
import com.googlecode.objectify.util.cmd.ObjectifyWrapper;
import edu.gatech.oad.rocket.findmythings.server.db.model.*;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.SimpleByteSource;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public abstract class DatabaseService {
@SuppressWarnings("unused")
private static final Logger LOGGER = Logger.getLogger(DatabaseService.class.getName());
private static final long REGISTRATION_VALID_DAYS = 1;
static {
ObjectifyService.setFactory(new DatabaseFactory());
}
public static class DatabaseFactory extends ObjectifyFactory {
/** Register our entity types*/
public DatabaseFactory() {
super();
register(DBItem.class);
register(DBMember.class);
register(DBUserCounter.class);
register(DBRegistrationTicket.class);
register(DBAuthenticationToken.class);
}
@Override
public Database begin() {
return new Database(super.begin());
}
}
/**
* @return our extension to Objectify
*/
public static Database ofy() {
return (Database)ObjectifyService.ofy();
}
public static class Database extends ObjectifyWrapper<Database, DatabaseFactory>
{
public Database(Objectify base) {
super(base);
}
private void changeUserCount(final long delta) {
transact(new VoidWork() {
@Override
public void vrun() {
DBUserCounter th = userCounter();
if (th == null) {
th = new DBUserCounter();
}
th.delta(delta);
save().entity(th);
}
});
}
/**
* Given a registration we have to retrieve it, and if its valid
* update the associated user and then delete the registration. This isn't
* transactional and we may end up with a dangling RegistrationString, which
* I can't see as too much of a problem, although they will need to be cleaned up with
* a task on a regular basis (after they expire)..
* @param code The registration code
* @param email the user name for the code
*/
public void register(final String code, final String email) {
register(memberWithEmail(email), code);
}
public void register(final DBMember user, final String code) {
if (user != null) {
boolean wasRegistered = user.isRegistered();
user.register();
updateMember(user, !wasRegistered);
}
deleteRegistrationTicket(code);
}
/**
* Save member with authorization information
* @param user Member instance (required)
* @param password New password for the user (optional, null not allowed)
*/
public void updateMember(DBMember user, String password) {
if (user == null) return;
if (password != null) user.setPassword(password);
ofy().save().entity(user);
}
/**
* Save user with authorization information
* @param user User
* @param changeCount should the user count be incremented
*/
public void updateMember(DBMember user, boolean changeCount) {
ofy().save().entity(user);
if (changeCount) {
changeUserCount(1);
}
}
public DBMember deleteMember(DBMember user) {
ofy().delete().entity(user);
changeUserCount(-1L);
return user;
}
public void deleteAuthenticationTokensForEmail(String userEmail) {
if (userEmail == null) return;
Iterable<Key<DBAuthenticationToken>> allKeys = ofy().load().type(DBAuthenticationToken.class).filter("email", userEmail).keys();
ofy().delete().keys(allKeys);
}
public String emailFromRegistrationCode(String code) {
DBRegistrationTicket reg = load().type(DBRegistrationTicket.class).id(code).get();
return (reg == null) ? null : (reg.isValid() ? reg.getEmail() : null);
}
public String emailFromAuthenticationToken(String token) {
DBAuthenticationToken auth = load().type(DBAuthenticationToken.class).id(token).get();
return (auth == null) ? null : auth.getEmail();
}
public DBMember memberFromAuthenticationToken(String token) {
DBAuthenticationToken auth = load().type(DBAuthenticationToken.class).id(token).get();
return (auth == null) ? null : load().type(DBMember.class).id(auth.getEmail()).get();
}
public DBMember memberWithEmail(String email) {
return load().type(DBMember.class).id(email).get();
}
DBUserCounter userCounter() {
return load().type(DBUserCounter.class).id(DBUserCounter.COUNTER_ID).get();
}
public long getUserCount() {
DBUserCounter th = userCounter();
if (th == null) return 0;
return th.getCount();
}
public Date getCountLastModified() {
DBUserCounter th = userCounter();
if (th == null) return new Date(0L);
return th.getLastModified();
}
/** Creation methods **/
private static final RandomNumberGenerator magic = new SecureRandomNumberGenerator();
public String createRegistrationTicket(String email) {
ByteSource salt = magic.nextBytes();
String ticket = new Sha256Hash(email, new SimpleByteSource(salt), 63).toHex().substring(0,10);
DBRegistrationTicket reg = new DBRegistrationTicket(ticket, email, REGISTRATION_VALID_DAYS, TimeUnit.DAYS);
save().entity(reg);
return ticket;
}
public String createAuthenticationToken(String email) {
DBAuthenticationToken auth = new DBAuthenticationToken(email);
save().entity(auth);
return auth.getIdentifierString();
}
public void deleteRegistrationTicket(String ticket) {
delete().type(DBRegistrationTicket.class).id(ticket);
}
public void deleteAuthenticationToken(String token) {
delete().type(DBAuthenticationToken.class).id(token);
}
public void deleteAuthenticationTokens(String... tokens) {
delete().type(DBAuthenticationToken.class).ids(tokens);
}
public void deleteAuthenticationTokens(Collection<String> tokens) {
delete().type(DBAuthenticationToken.class).ids(tokens);
}
}
}