/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.mail.internal.factory.template; import java.io.StringWriter; import java.util.Locale; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import javax.mail.MessagingException; import org.apache.velocity.VelocityContext; import org.xwiki.bridge.DocumentAccessBridge; import org.xwiki.component.annotation.Component; import org.xwiki.localization.LocaleUtils; import org.xwiki.model.EntityType; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.velocity.VelocityManager; import org.xwiki.velocity.XWikiVelocityException; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.web.ExternalServletURLFactory; import com.xpn.xwiki.web.XWikiURLFactory; /** * Default implementation evaluating template properties by taking them from {@code XWiki.Mail} and applying Velocity on * them. * * @version $Id: b7072c72da23a9b67d422b66206abd5dc8fc6e56 $ * @since 6.1RC1 */ @Component @Singleton public class DefaultMailTemplateManager implements MailTemplateManager { private static final EntityReference MAIL_CLASS = new EntityReference("Mail", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE)); private static final String LANGUAGE_PROPERTY_NAME = "language"; @Inject private DocumentAccessBridge documentBridge; @Inject private EntityReferenceSerializer<String> serializer; @Inject @Named("current") private DocumentReferenceResolver<EntityReference> resolver; @Inject private VelocityManager velocityManager; @Inject private Provider<XWikiContext> xwikiContextProvider; @Override public String evaluate(DocumentReference templateReference, String property, Map<String, Object> velocityVariables, Object localeValue) throws MessagingException { Locale locale = getLocale(localeValue); // Note: Make sure to use the class reference relative to the template's wiki and not the current wiki. DocumentReference mailClassReference = this.resolver.resolve(MAIL_CLASS, templateReference.getWikiReference()); VelocityContext velocityContext = createVelocityContext(velocityVariables); String templateFullName = this.serializer.serialize(templateReference); int objectNumber = getObjectMailNumber(templateReference, mailClassReference, locale); String content = this.documentBridge.getProperty(templateReference, mailClassReference, objectNumber, property).toString(); // Save the current URL Factory since we'll replace it with a URL factory that generates external URLs (ie // full URLs). XWikiContext xcontext = this.xwikiContextProvider.get(); XWikiURLFactory originalURLFactory = xcontext.getURLFactory(); try { xcontext.setURLFactory(new ExternalServletURLFactory(xcontext)); StringWriter writer = new StringWriter(); velocityManager.getVelocityEngine().evaluate(velocityContext, writer, templateFullName, content); return writer.toString(); } catch (XWikiVelocityException e) { throw new MessagingException(String.format( "Failed to evaluate property [%s] for Document [%s] and locale [%s]", property, templateReference, localeValue), e); } finally { xcontext.setURLFactory(originalURLFactory); } } @Override public String evaluate(DocumentReference templateReference, String property, Map<String, Object> data) throws MessagingException { return evaluate(templateReference, property, data, null); } /** * @return the number of the XWiki.Mail xobject with language xproperty is equal to the language parameter if not * exist return the XWiki.Mail xobject with language xproperty as default language if not exist return the first * XWiki.Mail xobject if there is only one XWiki.Mail xobject */ private int getObjectMailNumber(DocumentReference templateReference, DocumentReference mailClassReference, Locale language) throws MessagingException { int number = this.documentBridge.getObjectNumber(templateReference, mailClassReference, LANGUAGE_PROPERTY_NAME, language.getLanguage()); int mailObjectsCount = getMailObjectsCount(templateReference, mailClassReference); // Check that the language passed is not the default language if (!getDefaultLocale().equals(language) && number == -1) { number = this.documentBridge.getObjectNumber(templateReference, mailClassReference, LANGUAGE_PROPERTY_NAME, getDefaultLocale().getLanguage()); } if (mailObjectsCount == 1 && number == -1) { number = 0; } else if (mailObjectsCount == 0 && number == -1) { throw new MessagingException(String.format( "No [%s] object found in the Document [%s] for language [%s]", MAIL_CLASS.toString(), templateReference, language)); } else if (number == -1) { throw new MessagingException(String.format( "No [%s] object matches the locale [%s] or the default locale [%s] in the Document [%s]", MAIL_CLASS.toString(), language, getDefaultLocale(), templateReference)); } return number; } private int getMailObjectsCount(DocumentReference templateReference, DocumentReference mailClassReference) throws MessagingException { XWikiContext context = this.xwikiContextProvider.get(); int objectsCount; try { objectsCount = context.getWiki().getDocument(templateReference, context).getXObjects(mailClassReference) .size(); } catch (XWikiException e) { throw new MessagingException(String.format( "Failed to find number of [%s] objects in Document [%s]", mailClassReference, templateReference), e); } return objectsCount; } private VelocityContext createVelocityContext(Map<String, Object> velocityVariables) { // Note: We create an inner Velocity Context to make it read only and try to prevent the script to modify // its bindings. VelocityContext protect the inner VelocitContext but value could be modified directly // (when value are mutable, like Map or List for example). // However, this whole code is executed in a thread and we recreate the context for each email so it should be // pretty safe! VelocityContext existingVelocityContext = this.velocityManager.getVelocityContext(); VelocityContext velocityContext; if (existingVelocityContext != null) { velocityContext = new VelocityContext(existingVelocityContext); } else { velocityContext = new VelocityContext(); } if (velocityVariables != null) { for (Map.Entry<String, Object> velocityVariable : velocityVariables.entrySet()) { velocityContext.put(velocityVariable.getKey(), velocityVariable.getValue()); } } return velocityContext; } private Locale getDefaultLocale() { XWikiContext context = this.xwikiContextProvider.get(); return context.getWiki().getDefaultLocale(context); } private Locale getLocale(Object languageValue) { Locale locale; if ((languageValue == null) || ((languageValue instanceof Locale) && Locale.ROOT.equals(languageValue))) { locale = getDefaultLocale(); } else { // Note: we support both a Locale type and String mostly for backward-compatibility reasons (the first // version of this API only supported String and we've moved to support Locale). if (languageValue instanceof Locale) { locale = (Locale) languageValue; } else { locale = LocaleUtils.toLocale(languageValue.toString()); } } return locale; } }