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

/src/resources/contact-form/src/java/org/wyona/yanel/impl/resources/contactform/ContactResourceV2.java

https://github.com/wyona/yanel
Java | 515 lines | 349 code | 68 blank | 98 comment | 107 complexity | 6cc82baaa5f1d9a53d1c6b1ffc90cde8 MD5 | raw file
  1. /*-
  2. * Copyright 2012 Wyona
  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.wyona.org/licenses/APACHE-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.wyona.yanel.impl.resources.contactform;
  17. import java.io.File;
  18. import java.io.FileInputStream;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.InputStream;
  22. import java.util.HashMap;
  23. import java.util.Map;
  24. import java.util.UUID;
  25. import java.util.regex.Pattern;
  26. import java.util.regex.Matcher;
  27. import javax.mail.MessagingException;
  28. import javax.servlet.http.Cookie;
  29. import javax.servlet.http.HttpServletRequest;
  30. import javax.xml.transform.Transformer;
  31. import javax.xml.transform.TransformerFactory;
  32. import javax.xml.transform.sax.SAXResult;
  33. import javax.xml.transform.sax.SAXTransformerFactory;
  34. import javax.xml.transform.sax.TransformerHandler;
  35. import javax.xml.transform.stream.StreamSource;
  36. import org.apache.logging.log4j.Logger;
  37. import org.apache.logging.log4j.LogManager;
  38. import org.apache.xml.resolver.tools.CatalogResolver;
  39. import org.apache.xml.serializer.Serializer;
  40. import org.apache.xml.utils.ListingErrorHandler;
  41. import org.wyona.yanel.core.Path;
  42. import org.wyona.yanel.core.Resource;
  43. import org.wyona.yanel.core.api.attributes.TrackableV1;
  44. import org.wyona.yanel.core.api.attributes.ViewableV1;
  45. import org.wyona.yanel.core.attributes.viewable.View;
  46. import org.wyona.yanel.core.attributes.viewable.ViewDescriptor;
  47. import org.wyona.yanel.core.attributes.tracking.TrackingInformationV1;
  48. import org.wyona.yanel.core.serialization.SerializerFactory;
  49. import org.wyona.yanel.core.source.ResourceResolver;
  50. import org.wyona.yanel.core.source.SourceResolver;
  51. import org.wyona.yanel.core.util.MailUtil;
  52. import org.wyona.yanel.core.util.PathUtil;
  53. import org.wyona.yanel.impl.resources.BasicXMLResource;
  54. import org.wyona.yanel.servlet.AccessLog;
  55. import org.wyona.yanel.core.attributes.tracking.TrackingInformationV1;
  56. import org.wyona.yarep.core.NoSuchNodeException;
  57. import org.wyona.yarep.core.RepositoryException;
  58. import org.wyona.yarep.core.RepositoryFactory;
  59. import org.wyona.yarep.util.RepoPath;
  60. import org.xml.sax.InputSource;
  61. import org.xml.sax.XMLReader;
  62. import org.xml.sax.helpers.XMLReaderFactory;
  63. import org.w3c.dom.Document;
  64. import org.w3c.dom.Element;
  65. import org.wyona.commons.xml.XMLHelper;
  66. /**
  67. * Simple contact form resource, such that a user can send a message/email to an 'administrator'
  68. */
  69. public class ContactResourceV2 extends BasicXMLResource implements TrackableV1 {
  70. private static Logger log = LogManager.getLogger(ContactResourceV2.class);
  71. private static final String NAMESPACE = "http://www.wyona.org/yanel/contact-message/1.0.0";
  72. private static final String MESSAGE_PARAM_NAME = "message-id";
  73. // Constants
  74. private static final String SMTP_HOST = "smtpHost";
  75. private static final String SMTP_PORT = "smtpPort";
  76. private static final String TO = "to";
  77. private static final String SUBJECT = "subject";
  78. private static final String FIRST_NAME = "firstname";
  79. private static final String LAST_NAME = "lastname";
  80. // Email validation
  81. //private String defaultEmailRegEx = "(\\w+)@(\\w+\\.)(\\w+)(\\.\\w+)*";
  82. private String defaultEmailRegEx = "\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*";
  83. // Tracking information
  84. private TrackingInformationV1 trackInfo;
  85. // Parameters passed to transformer
  86. private Map<String, String> params = new HashMap<String, String>();
  87. /**
  88. * @see org.wyona.yanel.impl.resources.BasicXMLResource#getContentXML(String)
  89. */
  90. @Override
  91. protected InputStream getContentXML(String viewId) throws Exception {
  92. // Set up tracking info
  93. if(trackInfo != null) {
  94. trackInfo.addTag("contact");
  95. trackInfo.setPageType("contact");
  96. } else {
  97. log.warn("Tracking information bean is null! Check life cycle of resource!");
  98. }
  99. String requestedMsgID = getEnvironment().getRequest().getParameter(MESSAGE_PARAM_NAME);
  100. if (requestedMsgID != null && viewId.equals("message")) {
  101. return getRealm().getRepository().getNode(getMessagePath(requestedMsgID)).getInputStream();
  102. }
  103. String email = request.getParameter("email");
  104. // Checking if form was submitted
  105. if(email == null || "".equals(email)) {
  106. // The form has not yet been submitted - no email is provided.
  107. log.debug("Form not submitted yet!");
  108. // Print back message
  109. if(request.getParameter("message") != null) {
  110. setParameter("message", request.getParameter("message"));
  111. }
  112. String[] tags = new String[1];
  113. tags[0] = "contact";
  114. if (trackInfo != null) {
  115. trackInfo.setRequestAction("view");
  116. } else {
  117. log.warn("Tracking information bean is null! Check life cycle of resource!");
  118. }
  119. // Abort
  120. return getXMLDocument();
  121. }
  122. // Checking if spamblock is implemented
  123. if(request.getParameter("spamblock_hidden") == null ||
  124. request.getParameter("spamblock_input") == null) {
  125. // Spamblock is missing, aborting.
  126. throw new Exception("There is no spamblock implemented in the form.");
  127. }
  128. // Verifying that spamblock matches
  129. if(!request.getParameter("spamblock_hidden").equals("TRyAg41n") ||
  130. !request.getParameter("spamblock_input").equals("8989890")) {
  131. // Spamblock does not match - abort.
  132. return getXMLDocument();
  133. }
  134. // Spamblock is verified, process form.
  135. String message = request.getParameter("message");
  136. // Set tags
  137. // TODO: Maybe add contact subject, etc to tags?
  138. String[] tags = new String[1];
  139. tags[0] = "contact";
  140. if(trackInfo != null) {
  141. if(message != null) {
  142. trackInfo.addTag(message);
  143. }
  144. trackInfo.addCustomField("e-mail", email);
  145. trackInfo.setRequestAction("submit");
  146. ContactBean contact = new ContactBean(getEnvironment().getRequest());
  147. if(contact.getFirstName() != null) {
  148. trackInfo.addCustomField(FIRST_NAME, contact.getFirstName());
  149. }
  150. if(contact.getLastName() != null) {
  151. trackInfo.addCustomField(LAST_NAME, contact.getLastName());
  152. }
  153. } else {
  154. log.warn("Tracking information bean is null! Check life cycle of resource!");
  155. }
  156. Cookie cookie = AccessLog.getYanelAnalyticsCookie(request);
  157. String cookieValue = null;
  158. if (cookie != null) {
  159. cookieValue = cookie.getValue();
  160. } else {
  161. log.warn("No Yanel analytics cookie set yet!");
  162. }
  163. // INFO: Save message on server
  164. String messageID = saveMessage(cookieValue);
  165. log.debug("Back link: " + getBackLink(messageID));
  166. // INFO: Now send email
  167. if (getResourceConfigProperty(TO) != null) {
  168. sendMail(messageID);
  169. } else {
  170. setParameter("error", "couldNotSendMail");
  171. log.warn("No email has been sent, because no 'TO' address configured!");
  172. }
  173. // Pass transformer paramters for output
  174. if(request.getParameter("company") != null) {
  175. setParameter("company", request.getParameter("company"));
  176. }
  177. if(request.getParameter("firstName") != null) {
  178. setParameter("firstName", request.getParameter("firstName"));
  179. }
  180. if(request.getParameter("lastName") != null) {
  181. setParameter("lastName", request.getParameter("lastName"));
  182. }
  183. if(request.getParameter("email") != null) {
  184. setParameter("email", email);
  185. }
  186. if(request.getParameter("address") != null) {
  187. setParameter("address", request.getParameter("address"));
  188. }
  189. if(request.getParameter("zipCity") != null) {
  190. setParameter("zipCity", request.getParameter("zipCity"));
  191. }
  192. if(request.getParameter("message") != null) {
  193. setParameter("message", message);
  194. }
  195. return getXMLDocument();
  196. }
  197. /**
  198. * Add transformer paramter.
  199. */
  200. private void setParameter(String key, String value) {
  201. params.put(key, value);
  202. }
  203. /**
  204. * @see org.wyona.yanel.impl.resources.BasicXMLResource#passTransformerParameters(Transformer)
  205. */
  206. @Override
  207. protected void passTransformerParameters(Transformer transformer) throws Exception {
  208. super.passTransformerParameters(transformer);
  209. for(Map.Entry<String, String> entry : params.entrySet()) {
  210. transformer.setParameter(entry.getKey(), entry.getValue());
  211. }
  212. }
  213. /**
  214. * Send e-mail to administrator of this contact form
  215. * @param cookieValue Yanel analytics cookie value (in order to connect clickstream with this message).
  216. */
  217. private String saveMessage(String cookieValue) throws Exception {
  218. String uuid = UUID.randomUUID().toString();
  219. Document doc = getMessageDocument(uuid, cookieValue);
  220. String messagePath = getMessagePath(uuid);
  221. if (!getRealm().getRepository().existsNode(messagePath)) {
  222. org.wyona.yarep.core.Node messageNode = org.wyona.yarep.util.YarepUtil.addNodes(getRealm().getRepository(), messagePath, org.wyona.yarep.core.NodeType.RESOURCE);
  223. XMLHelper.writeDocument(doc, messageNode.getOutputStream());
  224. } else {
  225. log.error("Node '" + messagePath + "' already exists!");
  226. }
  227. return uuid;
  228. }
  229. /**
  230. * Send e-mail to administrator of this contact form
  231. * @param messageID Message ID in order to link back from email to message enriched with additional information
  232. */
  233. private void sendMail(String messageID) throws Exception {
  234. String email = getEnvironment().getRequest().getParameter("email");
  235. if(email == null || "".equals(email)) {
  236. log.warn("No email set yet!");
  237. setParameter("error", "emailNotSet");
  238. return;
  239. }
  240. if(!validateEmail(email)) {
  241. log.debug(
  242. "Doesn't seem to be a valid email: " + email + " (according " +
  243. "to the following regular expression: " + getEmailRegEx() + ")");
  244. setParameter("error", "emailNotValid");
  245. return;
  246. }
  247. ContactBean contact = new ContactBean(request);
  248. String subject = getResourceConfigProperty(SUBJECT);
  249. if (subject == null) {
  250. subject = "Yanel Contact Resource: No subject specified";
  251. }
  252. String from = getResourceConfigProperty("from");
  253. if (from == null) {
  254. from = email;
  255. }
  256. String content = getBody(contact, messageID);
  257. String to = getResourceConfigProperty(TO);
  258. if(to == null) {
  259. // INFO: Also see conf/contact-form_en.properties
  260. setParameter("error", "smtpConfigError");
  261. return;
  262. }
  263. String smtpHost = getResourceConfigProperty(SMTP_HOST);
  264. String smtpPortAsString = getResourceConfigProperty(SMTP_PORT);
  265. try {
  266. if(smtpHost != null && smtpPortAsString != null) {
  267. int smtpPort = Integer.parseInt(smtpPortAsString);
  268. MailUtil.send(smtpHost, smtpPort, from, to, subject, content);
  269. setParameter("sent", "true");
  270. } else {
  271. // INFO: Use default settings of Yanel for smtp-host and smtp-port
  272. String replyTo = from;
  273. if(contact.getFirstName() != null || contact.getLastName() != null) {
  274. String sender = contact.getFirstName() + " " + contact.getLastName();
  275. MailUtil.send(from, sender, replyTo, to, subject, content);
  276. } else {
  277. MailUtil.send(from, replyTo, to, subject, content);
  278. }
  279. setParameter("sent", "true");
  280. }
  281. } catch(MessagingException e) {
  282. // There as an error delivering the email
  283. log.error(e, e);
  284. String cause = e.toString();
  285. if(cause.contains("MessagingException: Unknown SMTP")) {
  286. setParameter("error", "unknownHost");
  287. } else if(cause.contains("SendFailedException: Invalid Addresses")) {
  288. setParameter("error", "invalidAddress");
  289. } else {
  290. setParameter("error", "couldNotSendMail");
  291. }
  292. } catch(NumberFormatException nfe) {
  293. log.error(nfe);
  294. setParameter("error", "smtpPortNotCorrect");
  295. }
  296. }
  297. /**
  298. * this method checks if the specified email is valid against a regular expression
  299. * @param email
  300. * @return true if email is valid
  301. */
  302. private boolean validateEmail(String email) throws Exception {
  303. Pattern pattern = Pattern.compile(getEmailRegEx());
  304. Matcher matcher = pattern.matcher(email);
  305. return matcher.find();
  306. }
  307. private String getEmailRegEx() throws Exception {
  308. if(getResourceConfigProperty("email-validation-regex") != null) {
  309. return getResourceConfigProperty("email-validation-regex");
  310. }
  311. return defaultEmailRegEx;
  312. }
  313. /**
  314. * @see org.wyona.yanel.core.api.attributes.TrackableV1#doTrack(TrackingInformationV1)
  315. */
  316. public void doTrack(TrackingInformationV1 trackInfo) {
  317. this.trackInfo = trackInfo;
  318. }
  319. /**
  320. * Get email body. Please overwrite this method in order to customize email body.
  321. * @param contact Contact information
  322. * @param messageID Message ID
  323. */
  324. protected String getBody(ContactBean contact, String messageID){
  325. StringBuilder content = new StringBuilder("");
  326. if(contact.getCompany() != null) {
  327. content.append("Company: ");
  328. content.append(contact.getCompany());
  329. content.append("\n");
  330. }
  331. if(contact.getFirstName() != null) {
  332. content.append("Firstname: ");
  333. content.append(contact.getFirstName());
  334. content.append("\n");
  335. }
  336. if(contact.getLastName() != null) {
  337. content.append("Lastname: ");
  338. content.append(contact.getLastName());
  339. content.append("\n");
  340. }
  341. if(contact.getAddress() != null) {
  342. content.append("Address: ");
  343. content.append(contact.getAddress());
  344. content.append("\n");
  345. }
  346. if(contact.getCity() != null) {
  347. content.append("City: ");
  348. content.append(contact.getCity());
  349. content.append("\n");
  350. }
  351. if(contact.getEmail() != null) {
  352. content.append("E-Mail: ");
  353. content.append(contact.getEmail());
  354. content.append("\n\n");
  355. }
  356. if(contact.getMessage() != null) {
  357. content.append("Message:\n");
  358. content.append(contact.getMessage());
  359. content.append("\n\n");
  360. }
  361. content.append("Message ID: " + getBackLink(messageID));
  362. return content.toString();
  363. }
  364. /**
  365. * Get XML to start with
  366. * @return XML as InputStream
  367. */
  368. private InputStream getXMLDocument() throws Exception {
  369. File xmlFile = org.wyona.commons.io.FileUtil.file(rtd.getConfigFile().getParentFile().getAbsolutePath(), "htdocs" + File.separator + "xml" + File.separator + "contact-form.xml");
  370. return new java.io.FileInputStream(xmlFile.getAbsolutePath());
  371. //return new ByteArrayInputStream("<root/>".getBytes());
  372. }
  373. /**
  374. * Generate message as XML document
  375. * @param messageID Message ID
  376. * @param cookieValue Cookie which helps to associate clickstream of user with this message
  377. * @return message as XML document
  378. */
  379. private Document getMessageDocument(String messageID, String cookieValue) {
  380. Document doc = XMLHelper.createDocument(NAMESPACE, "message");
  381. Element rootEl = doc.getDocumentElement();
  382. rootEl.setAttributeNS(NAMESPACE, "yanel-analytics-cookie", cookieValue);
  383. rootEl.setAttributeNS(NAMESPACE, "uuid", messageID);
  384. ContactBean contact = new ContactBean(getEnvironment().getRequest());
  385. if(contact.getCompany() != null) {
  386. appendChild(rootEl, "company", contact.getCompany());
  387. }
  388. if(contact.getFirstName() != null) {
  389. appendChild(rootEl, FIRST_NAME, contact.getFirstName());
  390. }
  391. if(contact.getLastName() != null) {
  392. appendChild(rootEl, LAST_NAME, contact.getLastName());
  393. }
  394. if(contact.getAddress() != null) {
  395. appendChild(rootEl, "address", contact.getAddress());
  396. }
  397. if(contact.getCity() != null) {
  398. appendChild(rootEl, "city", contact.getCity());
  399. }
  400. if(contact.getEmail() != null) {
  401. appendChild(rootEl, "e-mail", contact.getEmail());
  402. }
  403. if(contact.getMessage() != null) {
  404. appendChild(rootEl, "body", contact.getMessage());
  405. }
  406. return doc;
  407. }
  408. /**
  409. * Append element with text node to another element
  410. * @param parent Parent element to which element will be appended
  411. * @param name Element name
  412. * @param value String value of element
  413. */
  414. private void appendChild(Element parent, String name, String value) {
  415. Element companyEl = parent.getOwnerDocument().createElementNS(NAMESPACE, name);
  416. parent.appendChild(companyEl);
  417. companyEl.appendChild(parent.getOwnerDocument().createTextNode(value));
  418. }
  419. /**
  420. * Get back link to Yanel
  421. * @param messageID Message ID
  422. */
  423. protected String getBackLink(String messageID) {
  424. String baseURL = "http://www.yanel.org";
  425. try {
  426. if (getResourceConfigProperty("back-link-base-url") != null) {
  427. baseURL = getResourceConfigProperty("back-link-base-url");
  428. } else {
  429. log.warn("No base URL parameter 'back-link-base-url' configured! Use '" + baseURL + "' as default.");
  430. }
  431. } catch(Exception e) {
  432. log.error(e, e);
  433. }
  434. String url = baseURL + getPath() + "?" + MESSAGE_PARAM_NAME + "=" + messageID + "&yanel.resource.viewid=message";
  435. // TODO: Differentiate between email as HTML and plain text
  436. //return "<a href=\"" + url + "\">" + messageID + "</a>";
  437. return url;
  438. }
  439. /**
  440. * Get message path
  441. * @param uuid Message ID
  442. */
  443. private String getMessagePath(String uuid) {
  444. String messagesBasePath = "/contact-messages"; // TODO: Make base path configurable
  445. return messagesBasePath + "/" + uuid + ".xml";
  446. }
  447. }