/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. *******************************************************************************/ package org.apache.ofbiz.common.email; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.URL; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import org.apache.ofbiz.base.component.ComponentConfig.WebappInfo; import org.apache.ofbiz.base.location.FlexibleLocation; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.template.FreeMarkerWorker; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.service.DispatchContext; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; import org.apache.ofbiz.webapp.OfbizUrlBuilder; import org.apache.ofbiz.webapp.WebAppUtil; import freemarker.template.TemplateException; /** * Provides generic services related to preparing and delivering notifications * via email. * <p> * To use the NotificationService, a message specific service should be * defined for a particular * <a href="http://freemarker.sourceforge.net/docs/dgui_quickstart_template.html"> * Freemarker Template</a> mapping the required fields of the template to the * required attributes of the service. * </p> * <p> * This service definition should extend the <code>sendNotificationInterface</code> * or the <code>prepareNotificationInterface</code> service interface * and simply invoke the associated method defined in this class. * </p> * <pre> * {@code * <service name="sendPoPickupNotification" engine="java" * location="org.apache.ofbiz.content.email.NotificationServices" * invoke="sendNotification"> * <description>Sends notification based on a message template</description> * <implements service="sendNotificationInterface"/> * <attribute name="orderId" type="String" mode="IN" optional="false"/> * </service> * } * </pre> * <p> * An optional parameter available to all message templates is * <code>baseUrl</code> which can either be specified when the service is * invoked or let the <code>NotificationService</code> attempt to resolve it * as best it can, see {@link #setBaseUrl(Delegator, String, Map) setBaseUrl(Map)} * for details on how this is achieved. * </p> * The following example shows what a simple notification message template, * associated with the above service, might contain: * <blockquote> * <pre> * Please use the following link to schedule a delivery date: * ${baseUrl}/ordermgr/control/schedulepo?orderId=${orderId}" * </pre> * </blockquote> * <p> * The template file must be found on the classpath at runtime and * match the "templateName" field passed to the service when it * is invoked. * </p> * <p> * For complex messages with a large number of dynamic fields, it may be wise * to implement a custom service that takes one or two parameters that can * be used to resolve the rest of the required fields and then pass them to * the {@link #prepareNotification(DispatchContext, Map) prepareNotification(DispatchContext, Map)} * or {@link #sendNotification(DispatchContext, Map) sendNotification(DispatchContext, Map)} * methods directly to generate or generate and send the notification respectively. * </p> */ public class NotificationServices { public static final String module = NotificationServices.class.getName(); public static final String resource = "CommonUiLabels"; /** * This will use the {@link #prepareNotification(DispatchContext, Map) prepareNotification(DispatchContext, Map)} * method to generate the body of the notification message to send * and then deliver it via the "sendMail" service. * <p> * If the "body" parameter is already specified, the message body generation * phase will be skipped and the notification will be sent with the * specified body instead. This can be used to combine both service * calls in a decoupled manner if other steps are required between * generating the message body and sending the notification. * * @param ctx The dispatching context of the service * @param context The map containing all the fields associated with * the sevice * @return A Map with the service response messages in it */ public static Map<String, Object> sendNotification(DispatchContext ctx, Map<String, ? extends Object> context) { LocalDispatcher dispatcher = ctx.getDispatcher(); Locale locale = (Locale) context.get("locale"); Map<String, Object> result = null; try { // see whether the optional 'body' attribute was specified or needs to be processed // nulls are handled the same as not specified String body = (String) context.get("body"); if (body == null) { // prepare the body of the notification email Map<String, Object> bodyResult = prepareNotification(ctx, context); // ensure the body was generated successfully if (bodyResult.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_SUCCESS)) { body = (String) bodyResult.get("body"); } else { // otherwise just report the error Debug.logError("prepareNotification failed: " + bodyResult.get(ModelService.ERROR_MESSAGE), module); body = null; } } // make sure we have a valid body before sending if (body != null) { // retain only the required attributes for the sendMail service Map<String, Object> emailContext = new LinkedHashMap<String, Object>(); emailContext.put("sendTo", context.get("sendTo")); emailContext.put("body", body); emailContext.put("sendCc", context.get("sendCc")); emailContext.put("sendBcc", context.get("sendBcc")); emailContext.put("sendFrom", context.get("sendFrom")); emailContext.put("subject", context.get("subject")); emailContext.put("sendVia", context.get("sendVia")); emailContext.put("sendType", context.get("sendType")); emailContext.put("contentType", context.get("contentType")); // pass on to the sendMail service result = dispatcher.runSync("sendMail", emailContext); } else { Debug.logError("Invalid email body; null is not allowed", module); result = ServiceUtil.returnError(UtilProperties.getMessage(resource, "CommonNotifyEmailInvalidBody", locale)); } } catch (GenericServiceException serviceException) { Debug.logError(serviceException, "Error sending email", module); result = ServiceUtil.returnError(UtilProperties.getMessage(resource, "CommonNotifyEmailDeliveryError", locale)); } return result; } /** * This will process the associated notification template definition * with all the fields contained in the given context and generate * the message body of the notification. * <p> * The result returned will contain the appropriate response * messages indicating success or failure and the OUT parameter, * "body" containing the generated message. * * @param ctx The dispatching context of the service * @param context The map containing all the fields associated with * the sevice * @return A new Map indicating success or error containing the * body generated from the template and the input parameters. */ public static Map<String, Object> prepareNotification(DispatchContext ctx, Map<String, ? extends Object> context) { Delegator delegator = ctx.getDelegator(); String templateName = (String) context.get("templateName"); Map<String, Object> templateData = UtilGenerics.checkMap(context.get("templateData")); String webSiteId = (String) context.get("webSiteId"); Locale locale = (Locale) context.get("locale"); Map<String, Object> result = null; if (templateData == null) { templateData = new LinkedHashMap<String, Object>(); } try { // ensure the baseURl is defined setBaseUrl(delegator, webSiteId, templateData); // initialize the template reader and processor URL templateUrl = FlexibleLocation.resolveLocation(templateName); if (templateUrl == null) { Debug.logError("Problem getting the template URL: " + templateName + " not found", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "CommonNotifyEmailProblemFindingTemplate", locale)); } // process the template with the given data and write // the email body to the String buffer Writer writer = new StringWriter(); FreeMarkerWorker.renderTemplate(templateUrl.toExternalForm(), templateData, writer); // extract the newly created body for the notification email String notificationBody = writer.toString(); // generate the successful response result = ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "CommonNotifyEmailMessageBodyGeneratedSuccessfully", locale)); result.put("body", notificationBody); } catch (IOException ie) { Debug.logError(ie, "Problems reading template", module); result = ServiceUtil.returnError(UtilProperties.getMessage(resource, "CommonNotifyEmailProblemReadingTemplate", locale)); } catch (TemplateException te) { Debug.logError(te, "Problems processing template", module); result = ServiceUtil.returnError(UtilProperties.getMessage(resource, "CommonNotifyEmailProblemProcessingTemplate", locale)); } return result; } /** * The expectation is that a lot of notification messages will include * a link back to one or more pages in the system, which will require knowledge * of the base URL to extrapolate. This method will ensure that the * <code>baseUrl</code> field is set in the given context. * <p> * If it has been specified a default <code>baseUrl</code> will be * set using a best effort approach. If it is specified in the * url.properties configuration files of the system, that will be * used, otherwise it will attempt resolve the fully qualified * local host name. * <p> * <i>Note:</i> I thought it might be useful to have some dynamic way * of extending the default properties provided by the NotificationService, * such as the <code>baseUrl</code>, perhaps using the standard * <code>ResourceBundle</code> java approach so that both classes * and static files may be invoked. * * @param context The context to check and, if necessary, set the * <code>baseUrl</code>. */ public static void setBaseUrl(Delegator delegator, String webSiteId, Map<String, Object> context) { // If the baseUrl was not specified we can do a best effort instead if (!context.containsKey("baseUrl")) { try { WebappInfo webAppInfo = null; if (webSiteId != null) { webAppInfo = WebAppUtil.getWebappInfoFromWebsiteId(webSiteId); } OfbizUrlBuilder builder = OfbizUrlBuilder.from(webAppInfo, delegator); StringBuilder newURL = new StringBuilder(); builder.buildHostPart(newURL, "", false); context.put("baseUrl", newURL.toString()); newURL = new StringBuilder(); builder.buildHostPart(newURL, "", true); context.put("baseSecureUrl", newURL.toString()); } catch (Exception e) { Debug.logWarning(e, "Exception thrown while adding baseUrl to context: ", module); } } } }