Java tutorial
/* * Copyright (c) 2009-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.application.djigzo.james.matchers; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.mail.MessagingException; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import mitm.application.djigzo.User; import mitm.application.djigzo.james.DjigzoMailAttributes; import mitm.application.djigzo.james.DjigzoMailAttributesImpl; import mitm.application.djigzo.james.MailAddressUtils; import mitm.application.djigzo.james.MessageOriginatorIdentifier; import mitm.application.djigzo.service.SystemServices; import mitm.application.djigzo.workflow.UserWorkflow; import mitm.application.djigzo.workflow.UserWorkflow.GetUserMode; import mitm.common.hibernate.DatabaseAction; import mitm.common.hibernate.DatabaseActionExecutor; import mitm.common.hibernate.DatabaseActionExecutorBuilder; import mitm.common.hibernate.DatabaseException; import mitm.common.hibernate.SessionManager; import mitm.common.mail.EmailAddressUtils; import mitm.common.properties.HierarchicalPropertiesException; import mitm.common.util.CollectionUtils; import mitm.common.util.PhoneNumberUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.text.StrBuilder; import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; import org.hibernate.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Matcher that can detect a telephone number in the subject. If a telephone number is found * the telephone number of the recipient is set to the subject telephone number. if there are * multiple recipient and the subject contains a telephone number the user telephone number * property is not set because it's impossible to match the number with the correct recipient. * * The telephone number is only detected if the subject ends with the telephone number (unless * the default telephone regular expression has been overridden). * * Examples of valid subjects with a telephone number: * * "test 123456" * "other +1234567 " * "hi! +(800)12(34)56" * * Examples of invalid subjects with a telephone number: * * "some subject" * "Hi 123" * "Hi 12345678 some text" * * @author Martijn Brinkers * */ public class SubjectPhoneNumber extends AbstractContextAwareDjigzoMatcher { private final static Logger logger = LoggerFactory.getLogger(SubjectPhoneNumber.class); /* * Default expression used to detect a phone number in the subject. This only matches on * lines where the line ends with the telehpone number. * * Matching examples: * * "test 123456" * "other +1234567 " * "hi! +(800)12(34)56" * * Non matching examples: * * "some subject" * "Hi 123" * "Hi 12345678 some text" */ private final static String DEFAULT_PHONE_NUMBER_EXPRESSION = "(\\+|\\s|^)([\\s\\-()]*\\d){6,25}\\s*$"; /* * The number of times a database action should be retried when a ConstraintViolation occurs */ private final static int ACTION_RETRIES = 3; /* * Pattern used to remove all non numbers */ private final static Pattern phoneCleanPattern = Pattern.compile("[^0-9]*"); /* * The pattern which is matched against the subject */ private Pattern phoneNumberPattern; /* * manages database sessions */ private SessionManager sessionManager; /* * Used for adding and retrieving users */ private UserWorkflow userWorkflow; /* * Used to execute database actions in a transaction */ private DatabaseActionExecutor actionExecutor; /* * Is used to identify the 'sender' (from or enveloped sender or...) of the message */ private MessageOriginatorIdentifier messageOriginatorIdentifier; /* * The key under which we store the activation context */ protected static final String ACTIVATION_CONTEXT_KEY = "phoneNumber"; @Override protected Logger getLogger() { return logger; } protected String getSubjectExpression() { String expression = getCondition(); if (StringUtils.isBlank(expression)) { expression = DEFAULT_PHONE_NUMBER_EXPRESSION; } return expression; } @Override public void init() { String expression = getSubjectExpression(); if (expression == null || "".equals(expression)) { throw new IllegalArgumentException("Expression is missing."); } phoneNumberPattern = Pattern.compile(expression); StrBuilder sb = new StrBuilder(); sb.append("phoneNumberPattern: "); sb.append(phoneNumberPattern); getLogger().info(sb.toString()); sessionManager = SystemServices.getSessionManager(); userWorkflow = SystemServices.getUserWorkflow(); messageOriginatorIdentifier = SystemServices.getMessageOriginatorIdentifier(); actionExecutor = DatabaseActionExecutorBuilder.createDatabaseActionExecutor(sessionManager); assert (actionExecutor != null); } private void setRecipientPhoneNumber(MailAddress recipient, String phoneNumber) throws AddressException, HierarchicalPropertiesException { String email = EmailAddressUtils.canonicalizeAndValidate(recipient.toInternetAddress().getAddress(), true); if (email == null) { /* * Should not happen because MailAddress should always be a valid email address */ logger.warn(recipient + " is not a valid email address."); return; } User user = userWorkflow.getUser(email, GetUserMode.CREATE_IF_NOT_EXIST); user.getUserPreferences().getProperties().setSMSPhoneNumber(phoneNumber); userWorkflow.makePersistent(user); } private String cleanPhoneNumber(String phoneNumber, String defaultCountryCode) { if (StringUtils.isBlank(phoneNumber)) { return null; } /* * We will assume that the regular expression used to match the phone number only matched * valid phone numbers. We will remove all non number characters */ phoneNumber = phoneCleanPattern.matcher(phoneNumber).replaceAll(""); /* * If a phone number starts with 0 we will add the default country code */ phoneNumber = PhoneNumberUtils.addCountryCode(phoneNumber, defaultCountryCode); phoneNumber = PhoneNumberUtils.filterAndValidatePhoneNumber(phoneNumber); return phoneNumber; } private boolean addPhoneNumberAction(final Mail mail, MailAddress recipient, String phoneNumber) throws MessagingException, HierarchicalPropertiesException { String defaultCountryCode = null; User user = null; InternetAddress originator = messageOriginatorIdentifier.getOriginator(mail); if (originator != null) { user = userWorkflow.getUser(originator.getAddress(), GetUserMode.CREATE_IF_NOT_EXIST); } if (user != null) { defaultCountryCode = user.getUserPreferences().getProperties().getPhoneDefaultCountryCode(); } phoneNumber = cleanPhoneNumber(phoneNumber, defaultCountryCode); boolean added = false; if (StringUtils.isNotEmpty(phoneNumber)) { setRecipientPhoneNumber(recipient, phoneNumber); added = true; } else { logger.warn("Phone number is missing."); } return added; } private boolean addPhoneNumber(final Mail mail, final MailAddress recipient, final String phoneNumber) throws DatabaseException { /* * addPhoneNumbersAction will be executed in a transaction. Under 'normal' circumstances this should never * result in a ConstraintViolationException. But, when a new user is added at the same time this action is executed * it can result in a ConstraintViolationException We therefore retry at max. COMMIT_RETRIES times when a * ConstraintViolationException occurs. */ return actionExecutor.executeTransaction(new DatabaseAction<Boolean>() { @Override public Boolean doAction(Session session) throws DatabaseException { Session previousSession = sessionManager.getSession(); sessionManager.setSession(session); try { return addPhoneNumberAction(mail, recipient, phoneNumber); } catch (MessagingException e) { throw new DatabaseException(e); } catch (HierarchicalPropertiesException e) { throw new DatabaseException(e); } finally { sessionManager.setSession(previousSession); } } }, ACTION_RETRIES); } @Override public Collection<MailAddress> matchMail(Mail mail) throws MessagingException { List<MailAddress> matchingRecipients = null; String phoneNumber = null; MimeMessage message = mail.getMessage(); /* * We need to check whether the original subject is stored in the mail attributes because * the check for the telephone number should be done on the original subject. The reason * for this is that the current subject can result in the detection of an incorrect * telephone number because the subject was changed because the subject trigger was * removed. * * Example: * * original subject is: "test 123 [encrypt] 123456". The subject is: "test 123 123456" after * the trigger has been removed. The telephone nr 123123456 is detected instead of 123456. * * See bug report: https://jira.djigzo.com/browse/GATEWAY-11 * */ DjigzoMailAttributes mailAttributes = new DjigzoMailAttributesImpl(mail); String originalSubject = mailAttributes.getOriginalSubject(); if (originalSubject == null) { originalSubject = message.getSubject(); } if (StringUtils.isNotBlank(originalSubject)) { Matcher matcher = phoneNumberPattern.matcher(originalSubject); if (matcher.find()) { phoneNumber = matcher.group(); /* * Remove the match and set the subject without the number. * * Note: the match should be removed from the current subject!! * * Note2: using substringBeforeLast is only correct if the pattern matches the end * of the subject (which is the default). If the pattern matches something in the * middle, substringBeforeLast should not be used. */ message.setSubject(StringUtils.substringBeforeLast(message.getSubject(), phoneNumber)); } } if (StringUtils.isNotBlank(phoneNumber)) { matchingRecipients = MailAddressUtils.getRecipients(mail); int nrOfRecipients = CollectionUtils.getSize(matchingRecipients); if (nrOfRecipients == 1) { try { boolean added = addPhoneNumber(mail, matchingRecipients.get(0), phoneNumber); if (!added) { /* * addPhoneNumbers has more thorough checks to see if the phone * number is valid and if not, there will be no matcj */ matchingRecipients = Collections.emptyList(); } } catch (DatabaseException e) { throw new MessagingException("Exception adding phone numbers.", e); } } else { /* * A telephone number was found but we cannot add the number to a users account * because we do not known to which recipient the number belongs. We will however * return all the recipients. */ logger.warn("Found " + nrOfRecipients + " recipients but only one is supported."); } } if (matchingRecipients == null) { matchingRecipients = Collections.emptyList(); } return matchingRecipients; } }