001/*
002 *  Copyright 2010 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.newsletter.daos;
017
018import java.util.Collection;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023
024import org.apache.avalon.framework.thread.ThreadSafe;
025import org.apache.ibatis.session.RowBounds;
026import org.apache.ibatis.session.SqlSession;
027
028import org.ametys.core.datasource.AbstractMyBatisDAO;
029import org.ametys.core.trace.ForensicLogger;
030
031/**
032 * DAO for accessing newsletters subscribers.
033 */
034public class SubscribersDAO extends AbstractMyBatisDAO implements ThreadSafe
035{
036    /** The Avalon role name. */
037    public static final String ROLE = SubscribersDAO.class.getName();
038    
039    private static final Map<String, String> __ORDER_TO_COL_MAPPING = Map.of(
040        "email", "Email",
041        "siteName", "Site_Name",
042        "categoryId", "Category",
043        "subscribedAt", "Subscribed_At",
044        "token", "Token"
045    );
046
047    /**
048     * Origin of the unsubscription
049     */
050    public enum UnsubscribeOrigin
051    {
052        /** the subscriber to action to unsubscribe */
053        SUBSCRIBER,
054        /** an administrator of the newsletter unsubscribed the subscriber */
055        ADMINISTRATOR,
056        /** a data policy removed the subscription */
057        DATAPOLICY,
058    }
059    
060    private List<Subscriber> _getSubscribers(Map<String, Object> params)
061    {
062        return _getSubscribers(params, null);
063    }
064    
065    private List<Subscriber> _getSubscribers(Map<String, Object> params, List<Map<String, Object>> sorts)
066    {
067        return _getSubscribers(params, sorts, RowBounds.DEFAULT);
068    }
069    
070    private List<Subscriber> _getSubscribers(Map<String, Object> params, List<Map<String, Object>> sorts, RowBounds rowBounds)
071    {
072        Map<String, Object> queryParams = new HashMap<>(params);
073        
074        if (sorts != null)
075        {
076            StringBuilder sortString = new StringBuilder();
077            
078            for (Map<String, Object> sort : sorts)
079            {
080                String property = (String) sort.get("property");
081                sortString.append(__ORDER_TO_COL_MAPPING.getOrDefault(property, property));
082                sortString.append(" ");
083                sortString.append(Optional.of("direction").map(sort::get).orElse("ASC"));
084                sortString.append(",");
085            }
086            
087            if (!sorts.isEmpty())
088            {
089                sortString.deleteCharAt(sortString.length() - 1);
090                queryParams.put("__order", sortString.toString());
091            }
092        }
093        
094        try (SqlSession session = getSession())
095        {
096            return session.selectList("Subscribers.getSubscribers", queryParams, rowBounds);
097        }
098    }
099
100    /**
101     * Get the whole list for subscribers
102     * @return The list for subscribers
103     */
104    public List<Subscriber> getSubscribers()
105    {
106        return _getSubscribers(Map.of());
107    }
108    
109    /**
110     * Get the whole list for subscribers
111     * @param sorts The sorts list
112     * @return The list for subscribers
113     */
114    public List<Subscriber> getSubscribers(List<Map<String, Object>> sorts)
115    {
116        return _getSubscribers(Map.of(), sorts);
117    }
118    
119    /**
120     * Get the subscribers to a newsletter category
121     * @param siteName The site name
122     * @param categoryId The newsletter category's id
123     * @return the subscribers
124     */
125    public List<Subscriber> getSubscribers (String siteName, String categoryId)
126    {
127        return _getSubscribers(
128                Map.of(
129                    "siteName", siteName,
130                    "category", categoryId
131                )
132            );
133    }
134    
135    /**
136     * Get the subscribers to a newsletter category
137     * @param siteName The site name
138     * @param categoryId The newsletter category's id
139     * @param sorts The sorts list
140     * @param offset The number of results to ignore.
141     * @param limit The maximum number of results to return.
142     * @return the subscribers
143     */
144    public List<Subscriber> getSubscribers (String siteName, String categoryId, List<Map<String, Object>> sorts, int offset, int limit)
145    {
146        return _getSubscribers(
147                Map.of(
148                    "siteName", siteName,
149                    "category", categoryId
150                ),
151                sorts,
152                new RowBounds(offset, limit)
153            );
154    }
155    
156    /**
157     * Get the subscribers count for a newsletter category
158     * @param siteName The site name
159     * @param categoryId The newsletter category's id
160     * @return the subscribers count
161     */
162    public int getSubscribersCount (String siteName, String categoryId)
163    {
164        Map<String, Object> params = new HashMap<>();
165        params.put("siteName", siteName);
166        params.put("category", categoryId);
167        
168        try (SqlSession session = getSession())
169        {
170            return (Integer) session.selectOne("Subscribers.getSubscribersCount", params);
171        }
172    }
173    
174    /**
175     * Get a subscriber to a newsletter category
176     * @param email The subscriber email
177     * @param siteName The site name
178     * @param categoryId The newsletter category's id
179     * @return the subscribers
180     */
181    public Subscriber getSubscriber (String email, String siteName, String categoryId)
182    {
183        return _getSubscribers(
184                Map.of(
185                    "email", email,
186                    "siteName", siteName,
187                    "category", categoryId
188                )
189            )
190            .stream()
191            .findFirst()
192            .orElse(null);
193    }
194    
195    /**
196     * Get a subscriber by his token
197     * @param token The user token
198     * @return the subscribers
199     */
200    public Subscriber getSubscriberByToken (String token)
201    {
202        return _getSubscribers(Map.of("token", token))
203            .stream()
204            .findFirst()
205            .orElse(null);
206    }
207    
208    /**
209     * Get the list of subscriptions for a given email and site name.
210     * @param email the email.
211     * @param siteName the site name.
212     * @return the list of subscriptions.
213     */
214    public List<Subscriber> getSubscriptions(String email, String siteName)
215    {
216        return _getSubscribers(
217                Map.of(
218                    "email", email,
219                    "siteName", siteName
220                )
221            );
222    }
223    
224    /**
225     * Subscribes to the newsletter
226     * @param subscriber The subscriber
227     */
228    public void subscribe (Subscriber subscriber)
229    {
230        try (SqlSession session = getSession(true))
231        {
232            session.insert("Subscribers.subscribe", subscriber);
233        }
234    }
235    
236    /**
237     * Insert several subscriptions to newsletters.
238     * @param subscribers a list of subscribers.
239     */
240    public void subscribe(Collection<Subscriber> subscribers)
241    {
242        try (SqlSession session = getSession())
243        {
244            for (Subscriber subscriber : subscribers)
245            {
246                session.insert("Subscribers.subscribe", subscriber);
247            }
248            
249            session.commit();
250        }
251    }
252    
253    
254    /**
255     * Insert several subscriptions to newsletters.
256     * @param newSubscribers the collection of subscribers to insert.
257     * @param removeSubscriptions the collection of subscription tokens to remove.
258     * @param origin the origin of the unsubscription
259     */
260    public void modifySubscriptions(Collection<Subscriber> newSubscribers, Collection<String> removeSubscriptions, UnsubscribeOrigin origin)
261    {
262        try (SqlSession session = getSession())
263        {
264            for (Subscriber subscriber : newSubscribers)
265            {
266                session.insert("Subscribers.subscribe", subscriber);
267            }
268            
269            for (String tokenToRemove : removeSubscriptions)
270            {
271                Subscriber subscriber = getSubscriberByToken(tokenToRemove);
272                if (subscriber != null)
273                {
274                    session.update("Subscribers.unsubscribe", tokenToRemove);
275                    ForensicLogger.info("newsletter.unsubscribe", Map.of("email", subscriber.getEmail(), "siteName", subscriber.getSiteName(), "category", subscriber.getCategoryId(), "origin", origin.name()), null);
276                }
277            }
278            
279            session.commit();
280        }
281    }
282    
283    /**
284     * Unsubscribes to the newsletter
285     * @param token The unique token
286     * @param origin the origin of the unsubscription
287     */
288    public void unsubscribe (String token, UnsubscribeOrigin origin)
289    {
290        try (SqlSession session = getSession(true))
291        {
292            Subscriber subscriber = getSubscriberByToken(token);
293            if (subscriber != null)
294            {
295                session.update("Subscribers.unsubscribe", token);
296                ForensicLogger.info("newsletter.unsubscribe", Map.of("email", subscriber.getEmail(), "siteName", subscriber.getSiteName(), "category", subscriber.getCategoryId(), "origin", origin.name()), null);
297            }
298        }
299    }
300    
301    /**
302     * Empty a category's subscribers.
303     * @param categoryId the category to empty.
304     * @param siteName the site name.
305     */
306    public void empty(String categoryId, String siteName)
307    {
308        Map<String, Object> params = new HashMap<>();
309        params.put("categoryId", categoryId);
310        params.put("siteName", siteName);
311        
312        try (SqlSession session = getSession(true))
313        {
314            session.delete("Subscribers.empty", params);
315        }
316    }
317    
318    /**
319     * Remove all subscriptions for a subscriber in a given site.
320     * @param email the category to empty.
321     * @param siteName the site name.
322     * @param origin the origin of the unsubscription
323     * @return the number of subscription removed
324     */
325    public int unsubscribe(String email, String siteName, UnsubscribeOrigin origin)
326    {
327        Map<String, Object> params = new HashMap<>();
328        params.put("email", email);
329        params.put("siteName", siteName);
330        
331        try (SqlSession session = getSession(true))
332        {
333            int result = session.delete("Subscribers.removeSubscriptionsByEmail", params);
334            if (result > 0)
335            {
336                ForensicLogger.info("newsletter.unsubscribe", Map.of("email", email, "siteName", siteName, "origin", origin.name()), null);
337            }
338            return result;
339        }
340    }
341}