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