Package com.zerodes.exchangesync.tasksource.rtm

Source Code of com.zerodes.exchangesync.tasksource.rtm.RtmTaskSourceImpl

package com.zerodes.exchangesync.tasksource.rtm;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zerodes.exchangesync.dto.NoteDto;
import com.zerodes.exchangesync.dto.TaskDto;
import com.zerodes.exchangesync.settings.Settings;
import com.zerodes.exchangesync.tasksource.TaskSource;

public class RtmTaskSourceImpl implements TaskSource {
  private static final Logger LOG = LoggerFactory.getLogger(RtmTaskSourceImpl.class);
 
  private static final String RTM_API_KEY = "0bcf4c7e3182ec34f45321512e576300";
  private static final String RTM_SHARED_SECRET = "fbf7a0bdb0011532";

  private static final String REST_HOST = "api.rememberthemilk.com";
  private static final String REST_AUTH_PATH = "/services/auth/";
  private static final String REST_METHOD_PATH = "/services/rest/";

  private static final String EXCHANGE_ID_NOTE_TITLE = "ExchangeID";
  private static final String ORIGINAL_SUBJECT_NOTE_TITLE = "Original Email Subject";
 
  private static final String INTERNAL_SETTING_FROB = "frob";
  private static final String INTERNAL_SETTING_AUTH_TOKEN = "authToken";

  private Settings settings;
  private final String defaultRtmListId;

  private enum RtmAuthStatus {
    NEEDS_USER_APPROVAL,
    NEEDS_AUTH_TOKEN,
    AUTHORIZED
  }

  public RtmTaskSourceImpl(final Settings settings) throws RtmServerException {
    this.settings = settings;
    LOG.info("Connecting to Remember The Milk...");
    switch (getAuthStatus()) {
    case NEEDS_USER_APPROVAL:
      throw new RuntimeException("Please go to the following URL to authorize application to sync with Remember The Milk: "
          + getAuthenticationUrl("write"));
    case NEEDS_AUTH_TOKEN:
      completeAuthentication();
    }
    this.defaultRtmListId = getIdForListName(settings.getUserSettings().rtmListName());
  }
 
  @Override
  public void addTask(final TaskDto task) throws Exception {
    // Add email tag
    task.addTag("email");

    // Add ExchangeID note
    final NoteDto exchangeIdNote = new NoteDto();
    exchangeIdNote.setTitle(EXCHANGE_ID_NOTE_TITLE);
    exchangeIdNote.setBody(task.getExchangeId());
    task.addNote(exchangeIdNote);

    // Add Original Subject note
    final NoteDto originalSubjectNote = new NoteDto();
    originalSubjectNote.setTitle(ORIGINAL_SUBJECT_NOTE_TITLE);
    originalSubjectNote.setBody(task.getName());
    task.addNote(originalSubjectNote);

    final String timelineId = createTimeline();
    addTask(timelineId, defaultRtmListId, task);
    LOG.info("Added RTM task " + task.getName());
  }

  @Override
  public void updateDueDate(final TaskDto task) throws Exception {
    final String timelineId = createTimeline();
    updateDueDate(timelineId, (RtmTaskDto) task);
    LOG.info("Updated RTM task due date for " + task.getName());
  }

  @Override
  public void updateCompletedFlag(final TaskDto task) throws Exception {
    final String timelineId = createTimeline();
    updateCompleteFlag(timelineId, (RtmTaskDto) task);
    if (task.isCompleted()) {
      LOG.info("Marked RTM task as completed for " + task.getName());
    } else {
      LOG.info("Marked RTM task as incomplete for " + task.getName());
    }
  }

  private RtmAuthStatus getAuthStatus() throws RtmServerException {
    if (StringUtils.isNotEmpty(settings.getInternalSetting(INTERNAL_SETTING_FROB))) {
      return RtmAuthStatus.NEEDS_AUTH_TOKEN;
    }
    if (StringUtils.isEmpty(settings.getInternalSetting(INTERNAL_SETTING_AUTH_TOKEN)) || !checkToken()) {
      return RtmAuthStatus.NEEDS_USER_APPROVAL;
    }
    return RtmAuthStatus.AUTHORIZED;
  }

  private URL getAuthenticationUrl(final String perms) throws RtmServerException {
    try {
      // Call getFrob
      final TreeMap<String, String> getFrobParams = new TreeMap<String, String>();
      getFrobParams.put("method", "rtm.auth.getFrob");
      final Document response = parseXML(getRtmUri(REST_METHOD_PATH, getFrobParams));
      final Node node = response.selectSingleNode("/rsp/frob");
      settings.setInternalSetting(INTERNAL_SETTING_FROB, node.getText());

      // Generate url
      final TreeMap<String, String> params = new TreeMap<String, String>();
      params.put("perms", perms);
      params.put("frob", settings.getInternalSetting(INTERNAL_SETTING_FROB));
      return getRtmUri(REST_AUTH_PATH, params).toURL();
    } catch (final MalformedURLException e) {
      throw new RuntimeException("Unable to get authentication url", e);
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to get authentication url", e);
    }
  }

  public void completeAuthentication() throws RtmServerException {
    if (settings.getInternalSetting(INTERNAL_SETTING_FROB) == null) {
      throw new RuntimeException("Unable to complete authentication unless in NEEDS_AUTH_TOKEN status.");
    }
    try {
      final TreeMap<String, String> params = new TreeMap<String, String>();
      params.put("method", "rtm.auth.getToken");
      params.put("frob", settings.getInternalSetting(INTERNAL_SETTING_FROB));
      final Document response = parseXML(getRtmUri(REST_METHOD_PATH, params));
      final Node node = response.selectSingleNode("/rsp/auth/token");
      settings.setInternalSetting(INTERNAL_SETTING_AUTH_TOKEN, node.getText());
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to complete authentication", e);
    } finally {
      settings.setInternalSetting(INTERNAL_SETTING_FROB, null);
    }
  }

  @SuppressWarnings("unchecked")
  private String getIdForListName(final String listName) throws RtmServerException {
    try {
      final Document response = parseXML(getRtmMethodUri("rtm.lists.getList"));
      final List<Node> listNodesList = response.selectNodes("/rsp/lists/list");
      for (final Node listNode : listNodesList) {
        final Node nameNode = listNode.selectSingleNode("@name");
        final Node idNode = listNode.selectSingleNode("@id");
        if (nameNode.getText().equals(listName)) {
          return idNode.getText();
        }
      }
      throw new RuntimeException("Unable to find list named " + listName);
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to retrieve list of lists", e);
    }
  }

  /**
   * Create a new timeline.
   *
   * @return timeline id
   * @throws RtmServerException
   */
  private String createTimeline() throws RtmServerException {
    try {
      final Document response = parseXML(getRtmMethodUri("rtm.timelines.create"));
      final Node node = response.selectSingleNode("/rsp/timeline");
      return node.getText();
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to create timeline", e);
    }
  }

  /**
   * Add a new task.
   *
   * @param timelineId
   * @param listId
   * @param task
   * @throws RtmServerException
   */
  private void addTask(final String timelineId, final String listId, final TaskDto task) throws RtmServerException {
    try {
      final TreeMap<String, String> addTaskParams = new TreeMap<String, String>();
      addTaskParams.put("timeline", timelineId);
      addTaskParams.put("list_id", listId);
      addTaskParams.put("name", task.getName());
      final Document response = parseXML(getRtmMethodUri("rtm.tasks.add", addTaskParams));
     
      final RtmTaskDto rtmTask = new RtmTaskDto();
      task.copyTo(rtmTask);
      final Node idNode = response.selectSingleNode("/rsp/list/taskseries/task/@id");
      rtmTask.setRtmTaskId(idNode.getText());
      final Node taskSeriesIdNode = response.selectSingleNode("/rsp/list/taskseries/@id");
      rtmTask.setRtmTimeSeriesId(taskSeriesIdNode.getText());
      rtmTask.setRtmListId(listId);
     
      // Set due date (if required)
      if (rtmTask.getDueDate() != null) {
        updateDueDate(timelineId, rtmTask);
      }
     
      // Set completed (if required)
      if (rtmTask.isCompleted()) {
        updateCompleteFlag(timelineId, rtmTask);
      }
     
      // Add tags (if required)
      if (!rtmTask.getTags().isEmpty()) {
        addTags(timelineId, rtmTask, rtmTask.getTags());
      }
     
      // Add notes (if required)
      if (!rtmTask.getNotes().isEmpty()) {
        for (final NoteDto note : rtmTask.getNotes()) {
          addNote(timelineId, rtmTask, note);
        }
      }
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to add task", e);
    }
  }

  /**
   * Update a task's due date.
   *
   * @param timelineId
   * @param task
   * @throws RtmServerException
   * @throws UnsupportedEncodingException
   */
  private void updateDueDate(final String timelineId, final RtmTaskDto task)
      throws RtmServerException, UnsupportedEncodingException {
    final TreeMap<String, String> setDueDateParams = new TreeMap<String, String>();
    setDueDateParams.put("task_id", task.getRtmTaskId());
    setDueDateParams.put("taskseries_id", task.getRtmTimeSeriesId());
    setDueDateParams.put("timeline", timelineId);
    setDueDateParams.put("list_id", task.getRtmListId());
    setDueDateParams.put("due", convertJodaDateTimeToString(task.getDueDate()));
    parseXML(getRtmMethodUri("rtm.tasks.setDueDate", setDueDateParams));
  }

  /**
   * Update a task's url.
   *
   * @param timelineId
   * @param task
   * @throws RtmServerException
   * @throws UnsupportedEncodingException
   */
  private void updateUrl(final String timelineId, final RtmTaskDto task)
      throws RtmServerException, UnsupportedEncodingException {
    final TreeMap<String, String> setUrlParams = new TreeMap<String, String>();
    setUrlParams.put("task_id", task.getRtmTaskId());
    setUrlParams.put("taskseries_id", task.getRtmTimeSeriesId());
    setUrlParams.put("timeline", timelineId);
    setUrlParams.put("list_id", task.getRtmListId());
    setUrlParams.put("url", task.getUrl());
    parseXML(getRtmMethodUri("rtm.tasks.setURL", setUrlParams));
  }

  /**
   * Add tags to a task.
   *
   * @param timelineId
   * @param task
   * @throws RtmServerException
   * @throws UnsupportedEncodingException
   */
  private void addTags(final String timelineId, final RtmTaskDto task, final Set<String> tags) throws RtmServerException,
      UnsupportedEncodingException {
    final TreeMap<String, String> addTagsParams = new TreeMap<String, String>();
    addTagsParams.put("task_id", task.getRtmTaskId());
    addTagsParams.put("taskseries_id", task.getRtmTimeSeriesId());
    addTagsParams.put("timeline", timelineId);
    addTagsParams.put("list_id", task.getRtmListId());
    addTagsParams.put("tags", StringUtils.join(tags, ","));
    parseXML(getRtmMethodUri("rtm.tasks.addTags", addTagsParams));
  }

  /**
   * Add a note to a task.
   *
   * @param timelineId
   * @param task
   * @throws RtmServerException
   * @throws UnsupportedEncodingException
   */
  private void addNote(final String timelineId, final RtmTaskDto task, final NoteDto note) throws RtmServerException,
      UnsupportedEncodingException {
    final TreeMap<String, String> addNoteParams = new TreeMap<String, String>();
    addNoteParams.put("task_id", task.getRtmTaskId());
    addNoteParams.put("taskseries_id", task.getRtmTimeSeriesId());
    addNoteParams.put("timeline", timelineId);
    addNoteParams.put("list_id", task.getRtmListId());
    addNoteParams.put("note_title", note.getTitle());
    addNoteParams.put("note_text", note.getBody());
    parseXML(getRtmMethodUri("rtm.tasks.notes.add", addNoteParams));
  }

  /**
   * Mark a task as completed or incomplete.
   *
   * @param timelineId
   * @param task
   * @throws RtmServerException
   */
  private void updateCompleteFlag(final String timelineId, final RtmTaskDto task) throws RtmServerException {
    try {
      final TreeMap<String, String> setCompletedParams = new TreeMap<String, String>();
      setCompletedParams.put("task_id", task.getRtmTaskId());
      setCompletedParams.put("taskseries_id", task.getRtmTimeSeriesId());
      setCompletedParams.put("timeline", timelineId);
      setCompletedParams.put("list_id", task.getRtmListId());
      if (task.isCompleted()) {
        parseXML(getRtmMethodUri("rtm.tasks.complete", setCompletedParams));
      } else {
        parseXML(getRtmMethodUri("rtm.tasks.uncomplete", setCompletedParams));
      }
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to add task", e);
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public Collection<TaskDto> getAllTasks() throws Exception {
    try {
      final List<TaskDto> results = new ArrayList<TaskDto>();
      final TreeMap<String, String> params = new TreeMap<String, String>();
      final Document response = parseXML(getRtmMethodUri("rtm.tasks.getList", params));
      final List<Node> taskListList = response.selectNodes("/rsp/tasks/list");
      for (final Node listNode : taskListList) {
        final Node listIdNode = listNode.selectSingleNode("@id");
        final List<Node> taskSeriesNodesList = listNode.selectNodes("taskseries");
        for (final Node taskSeriesNode : taskSeriesNodesList) {
          final Node timeSeriesIdNode = taskSeriesNode.selectSingleNode("@id");
          final Node lastModifiedNode = taskSeriesNode.selectSingleNode("@modified");
          final Node nameNode = taskSeriesNode.selectSingleNode("@name");
          final Node idNode = taskSeriesNode.selectSingleNode("task/@id");
          final Node dueNode = taskSeriesNode.selectSingleNode("task/@due");
          final Node completedNode = taskSeriesNode.selectSingleNode("task/@completed");
          final RtmTaskDto rtmTask = new RtmTaskDto();
          rtmTask.setRtmTaskId(idNode.getText());
          rtmTask.setRtmTimeSeriesId(timeSeriesIdNode.getText());
          rtmTask.setRtmListId(listIdNode.getText());
          rtmTask.setLastModified(convertStringToJodaDateTime(lastModifiedNode.getText()));
          rtmTask.setName(nameNode.getText());
          rtmTask.setDueDate(convertStringToJodaDateTime(dueNode.getText()));
          rtmTask.setCompleted(StringUtils.isNotEmpty(completedNode.getText()));
          final List<Node> tagNodes = taskSeriesNode.selectNodes("tags/tag");
          for (final Node tagNode : tagNodes) {
            rtmTask.addTag(tagNode.getText());
          }
          final List<Node> noteNodes = taskSeriesNode.selectNodes("notes/note");
          for (final Node noteNode : noteNodes) {
            final NoteDto note = new NoteDto();
            note.setTitle(noteNode.selectSingleNode("@title").getText());
            note.setBody(noteNode.getText());
            rtmTask.addNote(note);
            if (note.getTitle().equals(EXCHANGE_ID_NOTE_TITLE)) {
              rtmTask.setExchangeId(note.getBody());
            }
          }
          results.add(rtmTask);
        }
      }
      return results;
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to add task", e);
    }
  }

  private boolean checkToken() throws RtmServerException {
    try {
      final Document response = parseXML(getRtmMethodUri("rtm.auth.checkToken"));
      final Node tokenNode = response.selectSingleNode("/rsp/auth/token");
      final Node usernameNode = response.selectSingleNode("/rsp/auth/user/@username");
      LOG.info("Connected to Remember The Milk as " + usernameNode.getText());
      return tokenNode.getText().equals(settings.getInternalSetting(INTERNAL_SETTING_AUTH_TOKEN));
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to check token", e);
    }
  }

  private URI getRtmMethodUri(final String methodName) throws UnsupportedEncodingException {
    return getRtmMethodUri(methodName, new TreeMap<String, String>());
  }

  private URI getRtmMethodUri(final String methodName, final TreeMap<String, String> params) throws UnsupportedEncodingException {
    params.put("method", methodName);
    params.put("auth_token", settings.getInternalSetting(INTERNAL_SETTING_AUTH_TOKEN));
    return getRtmUri(REST_METHOD_PATH, params);
  }

  private URI getRtmUri(final String uriPath, final TreeMap<String, String> params) throws UnsupportedEncodingException {
    params.put("api_key", RTM_API_KEY);
    final StringBuilder uriString = new StringBuilder("http://" + REST_HOST + uriPath + "?");
    for (final String key : params.keySet()) {
      uriString.append(key).append("=").append(URLEncoder.encode(params.get(key), "UTF-8")).append("&");
    }
    uriString.append("api_sig").append("=").append(getApiSig(params));
    return URI.create(uriString.toString());
  }

  private Document parseXML(final URI uri) throws RtmServerException {
    final SAXReader reader = new SAXReader();
    final Document response;
    try {
      response = reader.read(uri.toURL());
      final Node status = response.selectSingleNode("/rsp/@stat");
      if (status != null) {
        if (status.getText().equals("fail")) {
          final Node errCode = response.selectSingleNode("/rsp/err/@code");
          final Node errMessage = response.selectSingleNode("/rsp/err/@msg");
          throw new RtmServerException(Integer.valueOf(errCode.getText()), errMessage.getText());
        }
      }
      return response;
    } catch (final MalformedURLException e) {
      throw new RuntimeException("A malformed URL was specified", e);
    } catch (final DocumentException e) {
      throw new RuntimeException("There was a problem parsing the response from RTM", e);
    }
  }

  private String getApiSig(final TreeMap<String, String> params) {
    final StringBuilder rawString = new StringBuilder(RTM_SHARED_SECRET);
    for (final String key : params.keySet()) {
      rawString.append(key);
      rawString.append(params.get(key));
    }
    try {
      final byte[] bytesOfMessage = rawString.toString().getBytes("UTF-8");
      final MessageDigest md = MessageDigest.getInstance("MD5");
      final byte[] thedigest = md.digest(bytesOfMessage);
      return new String(Hex.encodeHex(thedigest));
    } catch (final NoSuchAlgorithmException e) {
      throw new RuntimeException("Unable to create API signature", e);
    } catch (final UnsupportedEncodingException e) {
      throw new RuntimeException("Unable to create API signature", e);
    }
  }
 
  private String convertJodaDateTimeToString(final DateTime theDate) {
    final DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd");
    return fmt.print(theDate) + "T23:59:59Z";
  }

  private DateTime convertStringToJodaDateTime(final String theDate) {
    if (StringUtils.isEmpty(theDate)) {
      return null;
    }
    final DateTimeFormatter dateFormat = ISODateTimeFormat.dateTimeNoMillis();
    return new DateTime(dateFormat.parseDateTime(theDate).toDate());
  }
}
TOP

Related Classes of com.zerodes.exchangesync.tasksource.rtm.RtmTaskSourceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.