001/* 002 * Copyright 2011 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.subscribe; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.IOException; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.LinkedHashSet; 026import java.util.Map; 027import java.util.Set; 028import java.util.UUID; 029import java.util.regex.Pattern; 030 031import org.apache.avalon.framework.parameters.Parameters; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.cocoon.acting.ServiceableAction; 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Redirector; 037import org.apache.cocoon.environment.Request; 038import org.apache.cocoon.environment.SourceResolver; 039import org.apache.cocoon.servlet.multipart.Part; 040import org.apache.cocoon.servlet.multipart.PartOnDisk; 041import org.apache.cocoon.servlet.multipart.RejectedPart; 042import org.apache.commons.io.FilenameUtils; 043import org.apache.commons.io.IOUtils; 044import org.apache.commons.io.input.BOMInputStream; 045import org.apache.commons.lang.StringUtils; 046 047import org.ametys.plugins.newsletter.category.Category; 048import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint; 049import org.ametys.plugins.newsletter.daos.Subscriber; 050import org.ametys.plugins.newsletter.daos.SubscribersDAO; 051 052/** 053 * Import subscribers from a CSV or text file. 054 */ 055public class ImportSubscribersAction extends ServiceableAction 056{ 057 private static final String[] _ALLOWED_EXTENSIONS = new String[] {"txt", "csv"}; 058 059 private static final Pattern __EMAIL_VALIDATOR = Pattern.compile("^([a-z0-9._-]+@[a-z0-9.-]{2,}[.][a-zA-Z0-9]{2,})?$"); 060 061 /** The subscribers DAO. */ 062 protected SubscribersDAO _subscribersDao; 063 064 /** The category provider extension point. */ 065 protected CategoryProviderExtensionPoint _categoryProviderEP; 066 067 @Override 068 public void service(ServiceManager smanager) throws ServiceException 069 { 070 super.service(smanager); 071 _subscribersDao = (SubscribersDAO) smanager.lookup(SubscribersDAO.ROLE); 072 _categoryProviderEP = (CategoryProviderExtensionPoint) smanager.lookup(CategoryProviderExtensionPoint.ROLE); 073 } 074 075 @Override 076 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 077 { 078 Map<String, String> result = new HashMap<>(); 079 080 Request request = ObjectModelHelper.getRequest(objectModel); 081 082 String siteName = parameters.getParameter("siteName", request.getParameter("siteName")); 083 String categoryId = request.getParameter("categoryId"); 084 boolean cleanTable = "true".equals(request.getParameter("cleanSubscribers")); 085 086 Part part = (Part) request.get("importFile"); 087 if (part instanceof RejectedPart) 088 { 089 return Collections.singletonMap("error", "rejected-file"); 090 } 091 092 PartOnDisk uploadedFilePart = (PartOnDisk) part; 093 File uploadedFile = (uploadedFilePart != null) ? uploadedFilePart.getFile() : null; 094 String filename = (uploadedFilePart != null) ? uploadedFilePart.getFileName().toLowerCase() : null; 095 096 if (!FilenameUtils.isExtension(filename, _ALLOWED_EXTENSIONS)) 097 { 098 return Collections.singletonMap("error", "invalid-extension"); 099 } 100 101 // Test if the category exists. 102 Category category = _categoryProviderEP.getCategory(categoryId); 103 if (category == null) 104 { 105 return Collections.singletonMap("error", "unknown-category"); 106 } 107 108 FileInputStream fileIS = new FileInputStream(uploadedFile); 109 BOMInputStream bomIS = new BOMInputStream(fileIS); 110 111 // Extract the emails from the import file. 112 Collection<String> emails = getEmails(bomIS); 113 114 if (!emails.isEmpty()) 115 { 116 // Empty the category subscribers if needed. 117 if (cleanTable) 118 { 119 _subscribersDao.empty(categoryId, siteName); 120 } 121 122 // Insert the emails. 123 insertSubscribers(emails, categoryId, siteName, result); 124 } 125 126 result.put("success", "true"); 127 result.put("categoryId", categoryId); 128 129 return result; 130 } 131 132 /** 133 * Extract the emails from the file. 134 * @param bomIS the input stream 135 * @return a collection of the emails. 136 * @throws IOException if an error occurs reading the file. 137 */ 138 protected Collection<String> getEmails(BOMInputStream bomIS) throws IOException 139 { 140 Set<String> emails = new LinkedHashSet<>(); 141 142 for (String line : IOUtils.readLines(bomIS)) 143 { 144 String[] part = line.split(";"); 145 String email = StringUtils.trimToEmpty(part[0]); 146 if (__EMAIL_VALIDATOR.matcher(email.toLowerCase()).matches() && StringUtils.isNotBlank(email)) 147 { 148 emails.add(email); 149 } 150 } 151 152 return emails; 153 } 154 155 /** 156 * Insert subscribers 157 * @param emails the emails to insert 158 * @param categoryId the id of category 159 * @param siteName the site name of category 160 * @param result the result map 161 */ 162 protected void insertSubscribers(Collection<String> emails, String categoryId, String siteName, Map<String, String> result) 163 { 164 int subscribedCount = 0; 165 int existingCount = 0; 166 int errorCount = 0; 167 168 for (String email : emails) 169 { 170 try 171 { 172 if (_subscribersDao.getSubscriber(email, siteName, categoryId) == null) 173 { 174 Subscriber subscriber = new Subscriber(); 175 subscriber.setEmail(email); 176 subscriber.setSiteName(siteName); 177 subscriber.setCategoryId(categoryId); 178 subscriber.setSubscribedAt(new Date()); 179 180 // Generate unique token. 181 String token = UUID.randomUUID().toString(); 182 subscriber.setToken(token); 183 184 _subscribersDao.subscribe(subscriber); 185 186 if (getLogger().isInfoEnabled()) 187 { 188 getLogger().info("The user with email '" + email + "' subscribed to the newsletter with the token " + token); 189 } 190 191 subscribedCount++; 192 } 193 else 194 { 195 existingCount++; 196 } 197 } 198 catch (Exception e) 199 { 200 getLogger().error("Impossible to add as a subscriber the email " + email + " in category " + categoryId + " of site " + siteName, e); 201 errorCount++; 202 } 203 } 204 205 result.put("subscribedCount", Integer.toString(subscribedCount)); 206 result.put("existingCount", Integer.toString(existingCount)); 207 result.put("errorCount", Integer.toString(errorCount)); 208 } 209 210}