package gotnames.web;
import gotnames.Utils;
import gotnames.dm.KTrans;
import gotnames.dm.QueryBuilder;
import gotnames.dm.User;
import gotnames.web.Facebook.FbNotAuthorizedException;
import gotnames.web.st.GotNamesTask;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Logger;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.medallia.spider.SpiderServlet;
import com.medallia.tiny.Clock;
import com.medallia.tiny.Empty;
import com.medallia.tiny.ObjectProvider;
import com.medallia.tiny.ObjectProvider.ObjectFactory;
/**
* Extension of {@link SpiderServlet} for Got Names.
* {@link #registerObjects(ObjectProvider, RequestHandler)} is overridden to
* register the objects available to tasks.
* <p>
* The class {@link GotNamesTask} is the parent class of most tasks in Got Names
* (except for tasks where the user is not yet logged in).
*/
public class GotNamesServlet extends SpiderServlet {
private static final Logger LOG = Utils.getLog();
private static final String FACEBOOK_DEV_APP_ID = "121767821223439";
private static final String FACEBOOK_PROD_APP_ID = "183954231614743";
private String serverInfo;
private boolean development;
private static final PersistenceManagerFactory PM_FACTORY = JDOHelper.getPersistenceManagerFactory("transactions-optional");
@Override public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
serverInfo = getServletContext().getServerInfo();
development = serverInfo.contains("Development");
setDebugMode(development);
}
/** Thrown if a {@link User} object is requested by a task, but the user is not authenticated */
private static class NoAuthException extends RuntimeException {
}
@Override protected void handleException(HttpServletRequest req, HttpServletResponse res, Throwable t) throws IOException {
if (t instanceof NoAuthException) {
res.sendRedirect("/" + getDefaultURI());
} else if (t instanceof FbNotAuthorizedException) {
res.sendRedirect("/facebookProfilePic");
} else {
LOG.severe("Unexpected exception: " + t.getMessage());
super.handleException(req, res, t);
}
}
/** Object which handles authentication of a {@link User} */
public interface UserAuthenticator {
/**
* @return true if the user with the given token was successfully
* authenticated and the web session has been updated
*/
boolean authenticate(String token);
/**
* Method which should be called if the {@link User} object of the
* currently logged in user is changed since it is cached in the session
*/
void userUpdated(User user);
/**
* Logoff the currently logged on user.
*/
void logout();
}
private static final String USER_SESSION_KEY = User.class.getCanonicalName();
/**
* The available objects to tasks are:
* <ul>
*
* <li> {@link PersistenceManager}
* <li> {@link User} (if the user is logged in, otherwise redirects to the start page)
* <li> {@link UserAuthenticator}
* <li> {@link Facebook}
* </ul>
*/
@Override protected void registerObjects(final ObjectProvider injector, final RequestHandler rh) {
injector
.registerFactory(new ObjectFactory<PersistenceManager>() {
@Override public PersistenceManager make() {
PersistenceManager pm = PM_FACTORY.getPersistenceManager();
return pm;
}
})
.registerFactory(new ObjectFactory<User>() {
@Override public User make() {
HttpSession session = rh.getSession();
// check if there is an auth token
String authToken = rh.getInput("t", String.class);
User user;
if (authToken != null) {
PersistenceManager pm = PM_FACTORY.getPersistenceManager();
user = attemptLogon(pm, session, authToken);
} else {
user = (User) session.getAttribute(USER_SESSION_KEY);
}
if (user == null)
throw new NoAuthException();
return user;
}
})
.register(new UserAuthenticator() {
@Override public boolean authenticate(String token) {
User user = attemptLogon(PM_FACTORY.getPersistenceManager(), rh.getSession(), token);
return user != null;
}
@Override public void userUpdated(User user) {
rh.getSession().setAttribute(USER_SESSION_KEY, user);
}
@Override public void logout() {
rh.getSession().removeAttribute(USER_SESSION_KEY);
}
})
.registerFactory(new ObjectFactory<Facebook>() {
@Override public Facebook make() {
String appId = development ? FACEBOOK_DEV_APP_ID : FACEBOOK_PROD_APP_ID;
final String cookieName = "fbs_" + appId;
return new Facebook(appId, getAccessToken(cookieName, rh)) {
@Override public void logout() {
rh.removeCookieValue(cookieName);
rh.getSession().invalidate();
}
};
}
private String getAccessToken(String cookieName, RequestHandler rh) {
String str = rh.getCookieValue(cookieName);
if (str == null)
return null;
Map<String, String> cookies = Empty.hashMap();
for (String cv : str.split("&")) {
String[] kv = cv.split("=");
if (kv.length == 2)
cookies.put(kv[0], kv[1]);
}
return cookies.get("access_token");
}
})
;
}
private User attemptLogon(PersistenceManager pm, HttpSession session, String authToken) {
final User user = QueryBuilder.begin(pm, User.class).getSingleByField("authToken", authToken);
if (user == null)
return null;
new KTrans.Void(pm) {
@Override protected void run() {
user.setLastLogin(Clock.now());
pm.makePersistent(user);
}
}.go();
session.setAttribute(USER_SESSION_KEY, user);
return user;
}
@Override protected String getDefaultURI() {
return "register";
}
}