package org.fluxtream.connectors.evernote;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.StringTokenizer;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.fluxtream.core.Configuration;
import org.fluxtream.core.aspects.FlxLogger;
import org.fluxtream.core.auth.AuthHelper;
import org.fluxtream.core.auth.TrustRelationshipRevokedException;
import org.fluxtream.core.connectors.Connector;
import org.fluxtream.core.domain.ApiKey;
import org.fluxtream.core.domain.Guest;
import org.fluxtream.core.services.GuestService;
import org.fluxtream.core.services.JPADaoService;
import com.google.api.client.util.IOUtils;
import net.coobird.thumbnailator.Thumbnailator;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.EvernoteApi;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
*
* @author Candide Kemmler (candide@fluxtream.com)
*/
@Controller
@RequestMapping(value = "/evernote")
public class EvernoteController {
FlxLogger logger = FlxLogger.getLogger(EvernoteController.class);
public static final String EVERNOTE_SANDBOX_KEY = "evernote.sandbox";
private static final String EVERNOTE_SERVICE = "evernoteService";
private static final String EVERNOTE_REQUEST_TOKEN = "evernoteRequestToken";
private static final String EVERNOTE_RENEWTOKEN_APIKEYID = "evernote.renewtoken.apiKeyId";
private static final short DEFAULT_MAX_WIDTH = 600;
@Autowired
Configuration env;
@Autowired
GuestService guestService;
@Autowired
JPADaoService jpaDaoService;
@PersistenceContext
EntityManager em;
@RequestMapping(value = "/token")
public String getEvernoteToken(HttpServletRequest request) throws IOException, ServletException {
final Boolean sandbox = Boolean.valueOf(env.get(EVERNOTE_SANDBOX_KEY));
OAuthService service = new ServiceBuilder()
.provider(sandbox?EvernoteApi.Sandbox.class:EvernoteApi.class)
.apiKey(getConsumerKey())
.apiSecret(getConsumerSecret())
.callback(env.get("homeBaseUrl") + "evernote/upgradeToken")
.build();
request.getSession().setAttribute(EVERNOTE_SERVICE, service);
// Obtain the Authorization URL
Token requestToken = service.getRequestToken();
request.getSession().setAttribute(EVERNOTE_REQUEST_TOKEN, requestToken);
String authorizationUrl = service.getAuthorizationUrl(requestToken);
final String apiKeyIdParameter = request.getParameter("apiKeyId");
if (apiKeyIdParameter!=null)
request.getSession().setAttribute(EVERNOTE_RENEWTOKEN_APIKEYID, apiKeyIdParameter);
return "redirect:" + authorizationUrl;
}
@RequestMapping(value = "/upgradeToken")
public String upgradeToken(HttpServletRequest request) throws IOException {
final String code = request.getParameter("oauth_verifier");
Verifier verifier = new Verifier(code);
OAuthService service = (OAuthService)request.getSession().getAttribute(EVERNOTE_SERVICE);
Token requestToken = (Token)request.getSession().getAttribute(EVERNOTE_REQUEST_TOKEN);
Token accessToken = service.getAccessToken(requestToken, verifier);
final String token = accessToken.getToken();
Guest guest = AuthHelper.getGuest();
final Connector connector = Connector.getConnector("evernote");
ApiKey apiKey;
if (request.getSession().getAttribute(EVERNOTE_RENEWTOKEN_APIKEYID)!=null) {
final String apiKeyIdString = (String) request.getSession().getAttribute(EVERNOTE_RENEWTOKEN_APIKEYID);
long apiKeyId = Long.valueOf(apiKeyIdString);
apiKey = guestService.getApiKey(apiKeyId);
} else
apiKey = guestService.createApiKey(guest.getId(), connector);
guestService.populateApiKey(apiKey.getId());
guestService.setApiKeyAttribute(apiKey, "accessToken", token);
request.getSession().removeAttribute(EVERNOTE_REQUEST_TOKEN);
request.getSession().removeAttribute(EVERNOTE_SERVICE);
if (request.getSession().getAttribute(EVERNOTE_RENEWTOKEN_APIKEYID)!=null) {
request.getSession().removeAttribute(EVERNOTE_RENEWTOKEN_APIKEYID);
return "redirect:/app/tokenRenewed/evernote";
}
return "redirect:/app/from/evernote";
}
String getConsumerKey() {
return env.get("evernoteConsumerKey");
}
String getConsumerSecret() {
return env.get("evernoteConsumerSecret");
}
@RequestMapping(value="/res/{apiKeyId}/{guid}")
public void getResource(@PathVariable("apiKeyId") long apiKeyId,
@PathVariable("guid") String rawGuid,
HttpServletResponse response) throws IOException, TrustRelationshipRevokedException {
String guid = rawGuid;
Integer maxWidth = null;
if (rawGuid.indexOf("@")!=-1) {
guid = rawGuid.substring(0, rawGuid.indexOf("@"));
String formatSpecs = rawGuid.substring(guid.length());
StringTokenizer st = new StringTokenizer(formatSpecs, "=");
st.nextToken();
String w = st.nextToken();
maxWidth = Integer.valueOf(w);
}
// we want to reduce the size of images that are too big to be transported over http in a timely manner,
// so here we ask the db for the mime type of the requested resource and its width so that,
// if we are dealing with an image, we know if we need to make it smaller
final Query nativeQuery = em.createNativeQuery(String.format("SELECT mime, width FROM Facet_EvernoteResource WHERE apiKeyId=%s AND guid='%s'",
apiKeyId, guid));
final Object[] singleResult = (Object[])nativeQuery.getSingleResult();
if (singleResult==null){
logger.warn("no Facet_EvernoteResource row found for guid=" + guid);
response.sendError(404);
return;
}
// use the resource's mimetype to set the Content-Type of the response
final String mimeType = (String)singleResult[0];
response.setContentType(mimeType);
// retrieve the main data file associated with this resource
final String devKvsLocation = env.get("btdatastore.db.location");
if (devKvsLocation==null)
throw new RuntimeException("No btdatastore.db.location property was specified (local.properties)");
ApiKey apiKey = guestService.getApiKey(apiKeyId);
File resourceFile = EvernoteUpdater.getResourceFile(apiKey.getGuestId(), apiKeyId,
guid, EvernoteUpdater.MAIN_APPENDIX, mimeType, devKvsLocation);
if (!resourceFile.exists()) {
logger.warn("resource file was not found for guid=" + guid);
response.sendError(404);
return;
}
// if it's an image, maybe reduce its size
if (mimeType.indexOf("image")!=-1) {
short width = (Short) singleResult[1];
// if the width of the image is larger than our max, then use Thumbnailator
// to make it smaller and directly stream the result
// TODO: cache the resulting image
int specifiedWidth = maxWidth!=null?maxWidth:DEFAULT_MAX_WIDTH;
if (width>specifiedWidth) {
Thumbnailator.createThumbnail(new FileInputStream(resourceFile),
response.getOutputStream(), specifiedWidth,
Integer.MAX_VALUE);
return;
}
}
// stream the file's contents directly in the response
IOUtils.copy(new FileInputStream(resourceFile), response.getOutputStream());
}
@RequestMapping(value="/content/{apiKeyId}/{guid}")
public ModelAndView getContent(@PathVariable("apiKeyId") String apiKeyId,
@PathVariable("guid") String guid,
HttpServletResponse response) throws IOException {
ModelAndView mav = new ModelAndView("connectors/evernote/content");
final Query nativeQuery = em.createNativeQuery(String.format("SELECT htmlContent FROM Facet_EvernoteNote WHERE apiKeyId=%s AND guid='%s'",
apiKeyId,
guid));
String content = (String)nativeQuery.getSingleResult();
content = adjustImageSizeAttributes(content);
response.setContentType("text/html; charset=utf-8");
mav.addObject("content", content);
mav.addObject("guid", guid);
return mav;
}
@RequestMapping(value="/popup/{apiKeyId}/{guid}")
public ModelAndView getPopup(@PathVariable("apiKeyId") String apiKeyId,
@PathVariable("guid") String guid,
HttpServletResponse response) throws IOException {
ModelAndView mav = new ModelAndView("connectors/evernote/popup");
final Query nativeQuery = em.createNativeQuery(String.format("SELECT title FROM Facet_EvernoteNote WHERE apiKeyId=%s AND guid='%s'",
apiKeyId,
guid));
String title = (String)nativeQuery.getSingleResult();
response.setContentType("text/html; charset=utf-8");
mav.addObject("apiKeyId", apiKeyId);
mav.addObject("title", title);
mav.addObject("guid", guid);
return mav;
}
/**
* Parse the html string, detect img tags' width/height attribute and adapt them
* to our max allowed width
* @param html a note's html content
* @return
*/
private String adjustImageSizeAttributes(String html) {
Document doc = Jsoup.parse(html);
Elements e = doc.getElementsByTag("img");
for (Element element : e) {
final String width = element.attr("width");
if (StringUtils.isNotEmpty(width)&&Integer.valueOf(width)> DEFAULT_MAX_WIDTH) {
element.attr("width", String.valueOf(DEFAULT_MAX_WIDTH));
final String height = element.attr("height");
if (StringUtils.isNotEmpty(height)) {
float w = Float.valueOf(width);
float h = Float.valueOf(height);
float r = Float.valueOf(DEFAULT_MAX_WIDTH)/w;
element.attr("height", String.valueOf((int)(h*r)));
}
}
}
return doc.html();
}
}