/* * Copyright (c) 2010-2015 Evolveum * * Licensed 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 com.evolveum.midpoint.notifications.impl.api.transports; import com.evolveum.midpoint.model.common.expression.Expression; import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext; import com.evolveum.midpoint.model.common.expression.ExpressionFactory; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.notifications.api.NotificationManager; import com.evolveum.midpoint.notifications.api.events.Event; import com.evolveum.midpoint.notifications.api.transports.Message; import com.evolveum.midpoint.notifications.api.transports.Transport; import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismPropertyDefinitionImpl; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.xml.namespace.QName; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLEncoder; import java.util.Date; /** * @author mederly */ @Component public class SimpleSmsTransport implements Transport { private static final Trace LOGGER = TraceManager.getTrace(SimpleSmsTransport.class); private static final String NAME = "sms"; private static final String DOT_CLASS = SimpleSmsTransport.class.getName() + "."; @Autowired protected PrismContext prismContext; @Autowired protected ExpressionFactory expressionFactory; @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; @Autowired private NotificationManager notificationManager; @PostConstruct public void init() { notificationManager.registerTransport(NAME, this); } @Override public void send(Message message, String transportName, Event event, Task task, OperationResult parentResult) { OperationResult result = parentResult.createSubresult(DOT_CLASS + "send"); result.addCollectionOfSerializablesAsParam("message recipient(s)", message.getTo()); result.addParam("message subject", message.getSubject()); SystemConfigurationType systemConfiguration = NotificationFunctionsImpl.getSystemConfiguration(cacheRepositoryService, new OperationResult("dummy")); if (systemConfiguration == null || systemConfiguration.getNotificationConfiguration() == null) { String msg = "No notifications are configured. SMS notification to " + message.getTo() + " will not be sent."; LOGGER.warn(msg) ; result.recordWarning(msg); return; } String smsConfigName = transportName.length() > NAME.length() ? transportName.substring(NAME.length() + 1) : null; // after "sms:" SmsConfigurationType found = null; for (SmsConfigurationType smsConfigurationType: systemConfiguration.getNotificationConfiguration().getSms()) { if ((smsConfigName == null && smsConfigurationType.getName() == null) || (smsConfigName != null && smsConfigName.equals(smsConfigurationType.getName()))) { found = smsConfigurationType; break; } } if (found == null) { String msg = "SMS configuration '" + smsConfigName + "' not found. SMS notification to " + message.getTo() + " will not be sent."; LOGGER.warn(msg) ; result.recordWarning(msg); return; } SmsConfigurationType smsConfigurationType = found; String file = smsConfigurationType.getRedirectToFile(); if (file != null) { writeToFile(message, file, null, result); return; } if (smsConfigurationType.getGateway().isEmpty()) { String msg = "SMS gateway(s) are not defined, notification to " + message.getTo() + " will not be sent."; LOGGER.warn(msg) ; result.recordWarning(msg); return; } String from = smsConfigurationType.getDefaultFrom() != null ? smsConfigurationType.getDefaultFrom() : ""; if (message.getTo().isEmpty()) { String msg = "There is no recipient to send the notification to."; LOGGER.warn(msg) ; result.recordWarning(msg); return; } String to = message.getTo().get(0); if (message.getTo().size() > 1) { String msg = "Currently it is possible to send the SMS to one recipient only. Among " + message.getTo() + " the chosen one is " + to + " (the first one)."; LOGGER.warn(msg) ; } for (SmsGatewayConfigurationType smsGatewayConfigurationType : smsConfigurationType.getGateway()) { OperationResult resultForGateway = result.createSubresult(DOT_CLASS + "send.forGateway"); resultForGateway.addContext("gateway name", smsGatewayConfigurationType.getName()); try { String url = evaluateExpressionChecked(smsGatewayConfigurationType.getUrl(), getDefaultVariables(from, to, message), "sms gateway url", task, result); LOGGER.debug("Sending SMS to URL " + url); if (smsGatewayConfigurationType.getRedirectToFile() != null) { writeToFile(message, smsGatewayConfigurationType.getRedirectToFile(), url, resultForGateway); result.computeStatus(); return; } else { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET); ClientHttpResponse response = request.execute(); LOGGER.debug("Result: " + response.getStatusCode() + "/" + response.getStatusText()); if (response.getStatusCode().series() != HttpStatus.Series.SUCCESSFUL) { throw new SystemException("SMS gateway communication failed: " + response.getStatusCode() + ": " + response.getStatusText()); } LOGGER.info("Message sent successfully to " + message.getTo() + " via gateway " + smsGatewayConfigurationType.getName() + "."); resultForGateway.recordSuccess(); result.recordSuccess(); return; } } catch (Throwable t) { String msg = "Couldn't send SMS to " + message.getTo() + " via " + smsGatewayConfigurationType.getName() + ", trying another gateway, if there is any"; LoggingUtils.logException(LOGGER, msg, t); resultForGateway.recordFatalError(msg, t); } } LOGGER.warn("No more SMS gateways to try, notification to " + message.getTo() + " will not be sent.") ; result.recordWarning("Notification to " + message.getTo() + " could not be sent."); } private void writeToFile(Message message, String file, String url, OperationResult result) { try { TransportUtil.appendToFile(file, formatToFile(message, url)); result.recordSuccess(); } catch (IOException e) { LoggingUtils.logException(LOGGER, "Couldn't write to SMS redirect file {}", e, file); result.recordPartialError("Couldn't write to SMS redirect file " + file, e); } } private String formatToFile(Message mailMessage, String url) { return "================ " + new Date() + " ======= " + (url != null ? url : "") + "\n" + mailMessage.toString() + "\n\n"; } private String evaluateExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, Task task, OperationResult result) { Throwable failReason; try { return evaluateExpression(expressionType, expressionVariables, shortDesc, task, result); } catch (ObjectNotFoundException e) { failReason = e; } catch (SchemaException e) { failReason = e; } catch (ExpressionEvaluationException e) { failReason = e; } LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); throw new SystemException(failReason); } private String evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException { QName resultName = new QName(SchemaConstants.NS_C, "result"); PrismPropertyDefinition<String> resultDef = new PrismPropertyDefinitionImpl(resultName, DOMUtil.XSD_STRING, prismContext); Expression<PrismPropertyValue<String>,PrismPropertyDefinition<String>> expression = expressionFactory.makeExpression(expressionType, resultDef, shortDesc, task, result); ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task, result); PrismValueDeltaSetTriple<PrismPropertyValue<String>> exprResult = ModelExpressionThreadLocalHolder .evaluateExpressionInContext(expression, params, task, result); if (exprResult.getZeroSet().size() != 1) { throw new SystemException("Invalid number of return values (" + exprResult.getZeroSet().size() + "), expected 1."); } return exprResult.getZeroSet().iterator().next().getValue(); } protected ExpressionVariables getDefaultVariables(String from, String to, Message message) throws UnsupportedEncodingException { ExpressionVariables variables = new ExpressionVariables(); variables.addVariableDefinition(SchemaConstants.C_FROM, from); variables.addVariableDefinition(SchemaConstants.C_TO, to); variables.addVariableDefinition(SchemaConstants.C_ENCODED_MESSAGE_TEXT, URLEncoder.encode(message.getBody(), "US-ASCII")); variables.addVariableDefinition(SchemaConstants.C_MESSAGE, message); return variables; } @Override public String getDefaultRecipientAddress(UserType recipient) { return recipient.getTelephoneNumber(); } @Override public String getName() { return NAME; } }