PageRenderTime 35ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/server/src/main/java/org/eurekastreams/server/service/email/MessageProcessor.java

http://github.com/lmco/eurekastreams
Java | 337 lines | 181 code | 30 blank | 126 comment | 24 complexity | 5f6e95f85708916c9120089359da6491 MD5 | raw file
  1. /*
  2. * Copyright (c) 2011 Lockheed Martin Corporation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.eurekastreams.server.service.email;
  17. import java.io.IOException;
  18. import java.util.List;
  19. import java.util.Map;
  20. import javax.mail.Address;
  21. import javax.mail.Message;
  22. import javax.mail.Message.RecipientType;
  23. import javax.mail.MessagingException;
  24. import javax.mail.internet.InternetAddress;
  25. import org.eurekastreams.commons.actions.context.DefaultPrincipal;
  26. import org.eurekastreams.commons.actions.context.PrincipalActionContext;
  27. import org.eurekastreams.commons.actions.context.service.ServiceActionContext;
  28. import org.eurekastreams.commons.actions.service.ServiceAction;
  29. import org.eurekastreams.commons.actions.service.TaskHandlerServiceAction;
  30. import org.eurekastreams.commons.exceptions.ExecutionException;
  31. import org.eurekastreams.commons.exceptions.ValidationException;
  32. import org.eurekastreams.commons.server.UserActionRequest;
  33. import org.eurekastreams.commons.server.service.ActionController;
  34. import org.eurekastreams.server.persistence.mappers.DomainMapper;
  35. import org.eurekastreams.server.search.modelview.PersonModelView;
  36. import org.springframework.beans.factory.BeanFactory;
  37. import org.springframework.transaction.PlatformTransactionManager;
  38. import org.springframework.transaction.TransactionStatus;
  39. import org.springframework.transaction.support.DefaultTransactionDefinition;
  40. /**
  41. * Responsible for all processing of a single received email message.
  42. */
  43. public class MessageProcessor
  44. {
  45. /** For getting the user's content from the message. */
  46. private final MessageContentExtractor messageContentExtractor;
  47. /** Determines which action to execute. */
  48. private final ActionSelectorFactory actionSelector;
  49. /** Instance of {@link ActionController} used to run actions. */
  50. private final ActionController serviceActionController;
  51. /** The context from which this service can load action beans. */
  52. private final BeanFactory beanFactory;
  53. /** For decoding the token. */
  54. private final TokenEncoder tokenEncoder;
  55. /** Parses the token content. */
  56. private final TokenContentFormatter tokenContentFormatter;
  57. /** Transaction manager (to allow calling mappers). */
  58. private final PlatformTransactionManager transactionMgr;
  59. /** DAO to get a user's person ID given their email address. */
  60. private final DomainMapper<String, Long> personIdByEmailDao;
  61. /** DAO to get a user's crypto key given their person ID. */
  62. private final DomainMapper<Long, byte[]> userKeyByIdDao;
  63. /** DAO to get a person by ID. */
  64. private final DomainMapper<Long, PersonModelView> personDao;
  65. /** Responds to messages that failed execution with result status. */
  66. private final MessageReplier messageReplier;
  67. /** Address messages must be sent to. */
  68. private final String requiredToAddress;
  69. /** Text address must begin with to be the desired To address. */
  70. private final String toEmailRequiredStart;
  71. /** Text address must end with to be the desired To address. */
  72. private final String toEmailRequiredEnd;
  73. /**
  74. * Constructor.
  75. *
  76. * @param inMessageContentExtractor
  77. * For getting the user's content from the message.
  78. * @param inActionSelector
  79. * Determines which action to execute.
  80. * @param inServiceActionController
  81. * Instance of {@link ActionController} used to run actions.
  82. * @param inBeanFactory
  83. * The context from which this service can load action beans.
  84. * @param inTokenEncoder
  85. * For decoding the token.
  86. * @param inTokenContentFormatter
  87. * Parses the token content.
  88. * @param inTransactionMgr
  89. * Transaction manager (to allow calling mappers).
  90. * @param inPersonIdByEmailDao
  91. * DAO to get a user's person ID given their email address.
  92. * @param inUserKeyByIdDao
  93. * DAO to get a user's crypto key given their person ID.
  94. * @param inPersonDao
  95. * DAO to get a person by ID.
  96. * @param inMessageReplier
  97. * Responds to messages that failed execution with result status.
  98. * @param inRequiredToAddress
  99. * Address messages must be sent to.
  100. */
  101. public MessageProcessor(final MessageContentExtractor inMessageContentExtractor,
  102. final ActionSelectorFactory inActionSelector, final ActionController inServiceActionController,
  103. final BeanFactory inBeanFactory, final TokenEncoder inTokenEncoder,
  104. final TokenContentFormatter inTokenContentFormatter, final PlatformTransactionManager inTransactionMgr,
  105. final DomainMapper<String, Long> inPersonIdByEmailDao, final DomainMapper<Long, byte[]> inUserKeyByIdDao,
  106. final DomainMapper<Long, PersonModelView> inPersonDao, final MessageReplier inMessageReplier,
  107. final String inRequiredToAddress)
  108. {
  109. messageContentExtractor = inMessageContentExtractor;
  110. actionSelector = inActionSelector;
  111. serviceActionController = inServiceActionController;
  112. beanFactory = inBeanFactory;
  113. tokenEncoder = inTokenEncoder;
  114. tokenContentFormatter = inTokenContentFormatter;
  115. transactionMgr = inTransactionMgr;
  116. personIdByEmailDao = inPersonIdByEmailDao;
  117. userKeyByIdDao = inUserKeyByIdDao;
  118. personDao = inPersonDao;
  119. messageReplier = inMessageReplier;
  120. requiredToAddress = inRequiredToAddress;
  121. int pos = inRequiredToAddress.indexOf('@');
  122. toEmailRequiredStart = inRequiredToAddress.substring(0, pos) + "+";
  123. toEmailRequiredEnd = inRequiredToAddress.substring(pos);
  124. }
  125. /**
  126. * Processes the message.
  127. *
  128. * @param message
  129. * Message to process.
  130. * @param inResponseMessages
  131. * List to add response messages to.
  132. * @return If message was processed.
  133. * @throws MessagingException
  134. * On error.
  135. * @throws IOException
  136. * On error.
  137. */
  138. public boolean execute(final Message message, final List<Message> inResponseMessages) throws MessagingException,
  139. IOException
  140. {
  141. String token = getToken(message);
  142. if (token == null)
  143. {
  144. return false;
  145. }
  146. String fromAddress = getFromAddress(message);
  147. // get the sender and sender's key
  148. DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
  149. transDef.setName("TokenAddressMessageAuthenticator");
  150. transDef.setReadOnly(false);
  151. TransactionStatus transStatus = transactionMgr.getTransaction(transDef);
  152. byte[] key;
  153. Long personId;
  154. PersonModelView person;
  155. try
  156. {
  157. personId = personIdByEmailDao.execute(fromAddress);
  158. key = userKeyByIdDao.execute(personId);
  159. person = personDao.execute(personId);
  160. }
  161. finally
  162. {
  163. transactionMgr.commit(transStatus);
  164. }
  165. // decrypt and unpack the token
  166. Map<String, Long> tokenData = getTokenData(token, key);
  167. // get the message content
  168. String content = messageContentExtractor.extract(message);
  169. // choose action to execute
  170. UserActionRequest actionSelection = actionSelector.select(tokenData, content, person);
  171. // execute action
  172. executeAction(message, actionSelection, person, inResponseMessages);
  173. return true;
  174. }
  175. /**
  176. * Decrypts and unpacks the token.
  177. *
  178. * @param token
  179. * Raw token.
  180. * @param key
  181. * User's key.
  182. * @return Data contained in token.
  183. */
  184. Map<String, Long> getTokenData(final String token, final byte[] key)
  185. {
  186. String tokenConent = tokenEncoder.decode(token, key);
  187. if (tokenConent == null)
  188. {
  189. throw new ValidationException("Cannot decrypt token for user.");
  190. }
  191. Map<String, Long> tokenData = tokenContentFormatter.parse(tokenConent);
  192. if (tokenData == null)
  193. {
  194. throw new ValidationException("Cannot parse token.");
  195. }
  196. return tokenData;
  197. }
  198. /**
  199. * Execute the selected action.
  200. *
  201. * @param message
  202. * The original message.
  203. * @param actionSelection
  204. * The selected action.
  205. * @param person
  206. * The user.
  207. * @param inResponseMessages
  208. * List to add response messages to.
  209. */
  210. void executeAction(final Message message, final UserActionRequest actionSelection, final PersonModelView person,
  211. final List<Message> inResponseMessages)
  212. {
  213. try
  214. {
  215. Object springBean = beanFactory.getBean(actionSelection.getActionKey());
  216. PrincipalActionContext actionContext = new ServiceActionContext(actionSelection.getParams(),
  217. new DefaultPrincipal(person.getAccountId(), person.getOpenSocialId(), person.getId()));
  218. actionContext.setActionId(actionSelection.getActionKey());
  219. if (springBean instanceof ServiceAction)
  220. {
  221. ServiceAction action = (ServiceAction) springBean;
  222. serviceActionController.execute(actionContext, action);
  223. }
  224. else if (springBean instanceof TaskHandlerServiceAction)
  225. {
  226. TaskHandlerServiceAction action = (TaskHandlerServiceAction) springBean;
  227. serviceActionController.execute(actionContext, action);
  228. }
  229. else
  230. {
  231. throw new ExecutionException("Bean '" + actionSelection.getActionKey()
  232. + "' is not an executable action");
  233. }
  234. }
  235. catch (RuntimeException ex)
  236. {
  237. // notify user on failure
  238. // Note: A response is only sent for errors processing the action (which could be due to missing content
  239. // from the message). This is because any errors encountered prior represent a bad sender or ill-formed
  240. // message or token and thus represent a suspicious message. In that case we don't want to send a reply, for
  241. // security.
  242. messageReplier.reply(message, person, actionSelection, ex, inResponseMessages);
  243. throw ex;
  244. }
  245. }
  246. /**
  247. * Extracts the FROM address from the message.
  248. *
  249. * @param message
  250. * The message.
  251. * @return The FROM address.
  252. * @throws MessagingException
  253. * On error.
  254. */
  255. String getFromAddress(final Message message) throws MessagingException
  256. {
  257. // insure the message has a From address
  258. Address[] addresses = message.getFrom();
  259. if (addresses == null || addresses.length != 1 || addresses[0] == null)
  260. {
  261. throw new ValidationException("Message must contain a single From address.");
  262. }
  263. return ((InternetAddress) addresses[0]).getAddress();
  264. }
  265. /**
  266. * Extracts the token from the message.
  267. *
  268. * @param message
  269. * The message.
  270. * @return The token.
  271. * @throws MessagingException
  272. * On error.
  273. */
  274. String getToken(final Message message) throws MessagingException
  275. {
  276. // insure the message has a To address which 1) matches the expected system address, and 2) has an address tag
  277. Address[] addresses = message.getRecipients(RecipientType.TO);
  278. if (addresses != null)
  279. {
  280. boolean noReplyFound = false;
  281. for (int i = 0; i < addresses.length; i++)
  282. {
  283. String addr = ((InternetAddress) addresses[i]).getAddress();
  284. // check for token-less system address: no-reply
  285. if (requiredToAddress.equals(addr))
  286. {
  287. noReplyFound = true;
  288. }
  289. // check for token
  290. else if (addr.startsWith(toEmailRequiredStart) && addr.endsWith(toEmailRequiredEnd))
  291. {
  292. String middle = addr.substring(toEmailRequiredStart.length(),
  293. addr.length() - toEmailRequiredEnd.length());
  294. if (tokenEncoder.couldBeToken(middle))
  295. {
  296. return middle;
  297. }
  298. }
  299. }
  300. if (noReplyFound)
  301. {
  302. return null;
  303. }
  304. }
  305. throw new ValidationException("Cannot find To address for the system with an address tag.");
  306. }
  307. }