001/*
002 *  Copyright 2014 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.sms.broker;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.text.SimpleDateFormat;
021import java.util.Date;
022import java.util.Set;
023
024import org.apache.avalon.framework.configuration.Configurable;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.cocoon.ProcessingException;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.commons.codec.digest.DigestUtils;
036import org.apache.commons.io.IOUtils;
037import org.apache.commons.io.input.BOMInputStream;
038import org.apache.commons.lang.StringUtils;
039import org.apache.http.Consts;
040import org.apache.http.HttpEntity;
041import org.apache.http.HttpResponse;
042import org.apache.http.client.config.RequestConfig;
043import org.apache.http.client.methods.HttpPost;
044import org.apache.http.entity.ContentType;
045import org.apache.http.entity.mime.MultipartEntityBuilder;
046import org.apache.http.entity.mime.content.InputStreamBody;
047import org.apache.http.entity.mime.content.StringBody;
048import org.apache.http.impl.client.CloseableHttpClient;
049import org.apache.http.impl.client.HttpClientBuilder;
050
051import org.ametys.core.user.UserIdentity;
052import org.ametys.core.user.UserManager;
053import org.ametys.runtime.config.Config;
054/*import org.apache.http.entity.mime.MultipartEntity;
055import org.apache.http.entity.mime.content.InputStreamBody;
056import org.apache.http.entity.mime.content.StringBody;*/
057
058/**
059 * Broker implementation using the "etoile diese" web service
060 */
061public class EtoileDieseBroker extends LoggerBroker implements Configurable, Contextualizable
062{
063    private static SimpleDateFormat __DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
064    
065    /** Url of Etoile diese's send sms web service in bulk mode (the recipients are listed in a CSV file) */
066    private String _etoileDieseBulkWSUrl;
067    
068    /** The Avalon context */
069    private Context _context;
070    
071    /** The users manager */
072    private UserManager _userManager;
073    
074    @Override
075    public void configure(Configuration configuration) throws ConfigurationException
076    {
077        _etoileDieseBulkWSUrl = configuration.getChild("etoilediese-bulk-ws-url").getValue();
078    }
079    
080    @Override
081    public void contextualize(Context context) throws ContextException
082    {
083        _context = context;
084    }
085    
086    @Override
087    public void service(ServiceManager manager) throws ServiceException
088    {
089        super.service(manager);
090        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
091    }
092    
093    @Override
094    public void send(Set<String> phoneNumbersList, String message, String listId) throws Exception
095    {   
096        super.send(phoneNumbersList, message, listId);
097        
098        String username = Config.getInstance().getValueAsString("org.ametys.plugins.sms.broker.user");
099        String password = Config.getInstance().getValueAsString("org.ametys.plugins.sms.broker.password");
100        String now = __DATE_FORMAT.format(new Date());
101        UserIdentity user = _currentUserProvider.getUser();
102        String userFullName = _userManager.getUser(user.getPopulationId(), user.getLogin()).getFullName();
103        
104        String computedPwd = DigestUtils.md5Hex(password + username + now);
105        
106        RequestConfig requestConfig = RequestConfig.custom().build();
107        
108        try (CloseableHttpClient httpclient = HttpClientBuilder.create().useSystemProperties().setDefaultRequestConfig(requestConfig).build(); 
109             InputStream csvFile = _getCSVFile(phoneNumbersList, message))
110        {
111            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
112            
113            // User
114            builder.addPart("u", new StringBody(username, ContentType.create("text/plain", Consts.UTF_8)));
115            // Password
116            builder.addPart("p", new StringBody(computedPwd, ContentType.create("text/plain", Consts.UTF_8)));
117            // Current date for MD5 sum 
118            builder.addPart("date", new StringBody(now, ContentType.create("text/plain", Consts.UTF_8)));
119            // Category
120            builder.addPart("c", new StringBody(getCategory(listId), ContentType.create("text/plain", Consts.UTF_8)));
121            // Message type
122            builder.addPart("f", new StringBody("sms", ContentType.create("text/plain", Consts.UTF_8)));
123            // CSV file
124            builder.addPart("file", new InputStreamBody(csvFile, ContentType.create("text/csv", Consts.UTF_8), "sms.csv"));
125            // Additionnal information
126            builder.addPart("sender-login", new StringBody(UserIdentity.userIdentityToString(user), ContentType.create("text/plain", Consts.UTF_8)));
127            builder.addPart("sender-fullname", new StringBody(userFullName, ContentType.create("text/plain", Consts.UTF_8)));
128            
129            HttpEntity entity = builder.build();
130            
131            // Prepare a request object
132            HttpPost postRequest = new HttpPost(_etoileDieseBulkWSUrl);
133            postRequest.setEntity(entity);
134            
135            // Execute the request
136            HttpResponse httpResponse = httpclient.execute(postRequest);
137            
138            if (httpResponse.getStatusLine().getStatusCode() == 200)
139            {
140                HttpEntity responseEntity = httpResponse.getEntity();
141                if (responseEntity != null)
142                {
143                    try (InputStream is = responseEntity.getContent();)
144                    {
145                        String response = IOUtils.toString(is, "ISO-8859-1");
146                        IOUtils.closeQuietly(is);
147                        
148                        if (response.contains("bulk"))
149                        {
150                            getLogger().info("EtoileDiese's response: " + response);
151                            return;
152                        }
153                        else
154                        {
155                            throw new ProcessingException("EtoileDiese failed to process delivering with the following message: " + response);
156                        }          
157                    }
158                }
159                throw new ProcessingException("EtoileDiese has not sent any response");
160            }
161            
162            throw new ProcessingException("Unable to call the web service: " + _etoileDieseBulkWSUrl + ", response: " + httpResponse.getStatusLine());
163        }
164    }
165    
166    @Override
167    public String getPhoneNumberFromStopRequest() throws Exception
168    {
169        Request request = ContextHelper.getRequest(_context);
170        String responseText = request.getParameter("txt");
171        String phoneNumber = request.getParameter("num");
172        
173        if (StringUtils.containsIgnoreCase(responseText, "stop"))
174        {
175            if (phoneNumber != null)
176            {
177                return "+" + phoneNumber;
178            }
179
180            throw new ProcessingException("EtoileDiese did not provide a phone number for the stop sms request");
181        }
182        
183        getLogger().info("The sms response of number [" + (phoneNumber != null ? phoneNumber : "unknown") + "] does not contain the 'stop' string: " + (responseText != null ? responseText : "[empty message]"));
184        return null;
185    }
186
187    /**
188     * Creates and return a CSV file from a list of numbers and a message
189     * @param phoneNumbersList the list of phone numbers to send a SMS to
190     * @param message the message to send
191     * @return the csv file
192     * @throws IOException if an error occurs while converting the list of phone numbers to an input stream
193     */
194    private InputStream _getCSVFile(Set<String> phoneNumbersList, String message) throws IOException
195    {
196        StringBuffer sb = new StringBuffer();
197        String banalizedNewLinesMessage = message.replace("\n", "\\n");
198        
199        for (String phoneNumber : phoneNumbersList)
200        {
201            sb.append(phoneNumber);
202            sb.append(" ; ");
203            sb.append(banalizedNewLinesMessage);
204            sb.append('\n');
205        }
206
207        return new BOMInputStream(IOUtils.toInputStream(sb.toString(), "UTF-8"));
208    }
209}