View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.transport.mailets;
21  
22  import org.apache.mailet.base.StringUtils;
23  import org.apache.mailet.base.GenericMailet;
24  import org.apache.mailet.Mail;
25  import org.apache.mailet.MailetException;
26  
27  import javax.mail.MessagingException;
28  import javax.mail.internet.ContentType;
29  
30  import java.io.BufferedReader;
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.FileNotFoundException;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.InputStreamReader;
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.regex.Pattern;
40  
41  /**
42   * Replace text contents
43   * <p>This mailet allow to specific regular expression to replace text in subject and content.
44   * 
45   * <p>
46   * Each expression is defined as:
47   * <code>/REGEX_PATTERN/SUBSTITUTION_PATTERN/FLAGS/</code>
48   * </p>
49   * 
50   * <p>
51   * <code>REGEX_PATTERN</code> is a regex used for the match<br>
52   * <code>SUBSTITUTION_PATTERN</code> is a substitution pattern<br>
53   * <code>FLAGS</code> flags supported for the pattern:<br>
54   *   i: case insensitive<br>
55   *   m: multi line<br>
56   *   x: extended (N/A)<br>
57   *   r: repeat - keep matching until a substitution is possible<br>
58   * </p>
59   * 
60   * <p>To identify subject and body pattern we use the tags &lt;subjectPattern&gt; and &lt;bodyPattern&gt;</p>
61   *
62   * <p>
63   * Rules can be specified in external files.
64   * Lines must be CRLF terminated and lines starting with # are considered commments.
65   * Tags used to include external files are &lt;subjectPatternFile&gt; and 
66   * &lt;bodyPatternFile&gt;
67   * If file path starts with # then the file is loaded as a reasource.
68   * </p>
69   * 
70   * <p>
71   * Use of both files and direct patterns at the same time is allowed.
72   * </p>
73   * 
74   * <p>
75   * This mailet allow also to enforce the resulting charset for messages processed.
76   * To do that the tag &lt;charset&gt; must be specified.
77   * </p>
78   * 
79   * <p>
80   * NOTE:
81   * Regexp rules must be escaped by regexp excaping rules and applying this 2 additional rules:<br>
82   * - "/" char inside an expression must be prefixed with "\":
83   *   e.g: "/\//-//" replaces "/" with "-"<br>
84   * - when the rules are specified using &lt;subjectPattern&gt; or &lt;bodyPattern&gt; and
85   *   "/,/" has to be used in a pattern string it must be prefixed with a "\".
86   *   E.g: "/\/\/,//" replaces "/" with "," (the rule would be "/\//,//" but the "/,/" must
87   *   be escaped.<br>
88   * </p>
89   */
90  public class ReplaceContent extends GenericMailet {
91      private static final String PARAMETER_NAME_SUBJECT_PATTERN = "subjectPattern";
92      private static final String PARAMETER_NAME_BODY_PATTERN = "bodyPattern";
93      private static final String PARAMETER_NAME_SUBJECT_PATTERNFILE = "subjectPatternFile";
94      private static final String PARAMETER_NAME_BODY_PATTERNFILE = "bodyPatternFile";
95      private static final String PARAMETER_NAME_CHARSET = "charset";
96      
97      public static final int FLAG_REPEAT = 1;
98      
99      private static class ReplaceConfig {
100         private Pattern[] subjectPatterns;
101         private String[] subjectSubstitutions;
102         private Integer[] subjectFlags;
103         private Pattern[] bodyPatterns;
104         private String[] bodySubstitutions;
105         private Integer[] bodyFlags;
106     }
107     
108     private String charset;
109     private int debug = 0;
110     
111     /**
112      * returns a String describing this mailet.
113      * 
114      * @return A desciption of this mailet
115      */
116     public String getMailetInfo() {
117         return "ReplaceContent";
118     }
119 
120     /**
121      * @return an array containing Pattern and Substitution of the input stream
122      * @throws MailetException 
123      */
124     protected static Object[] getPattern(String line) throws MailetException {
125         String[] pieces = StringUtils.split(line, "/");
126         if (pieces.length < 3) throw new MailetException("Invalid expression: " + line);
127         int options = 0;
128         //if (pieces[2].indexOf('x') >= 0) options += Pattern.EXTENDED;
129         if (pieces[2].indexOf('i') >= 0) options += Pattern.CASE_INSENSITIVE;
130         if (pieces[2].indexOf('m') >= 0) options += Pattern.MULTILINE;
131         if (pieces[2].indexOf('s') >= 0) options += Pattern.DOTALL;
132         
133         int flags = 0;
134         if (pieces[2].indexOf('r') >= 0) flags += FLAG_REPEAT;
135         
136         if (pieces[1].indexOf("\\r") >= 0) pieces[1] = pieces[1].replaceAll("\\\\r", "\r");
137         if (pieces[1].indexOf("\\n") >= 0) pieces[1] = pieces[1].replaceAll("\\\\n", "\n");
138         if (pieces[1].indexOf("\\t") >= 0) pieces[1] = pieces[1].replaceAll("\\\\t", "\t");
139         
140         return new Object[] {Pattern.compile(pieces[0], options), pieces[1] , new Integer(flags)};
141     }
142     
143     protected static List[] getPatternsFromString(String pattern) throws MailetException {
144         pattern = pattern.trim();
145         if (pattern.length() < 2 && !pattern.startsWith("/") && !pattern.endsWith("/")) throw new MailetException("Invalid parameter value: " + PARAMETER_NAME_SUBJECT_PATTERN);
146         pattern = pattern.substring(1, pattern.length() - 1);
147         String[] patternArray = StringUtils.split(pattern, "/,/");
148         
149         List patterns = new ArrayList();
150         List substitutions = new ArrayList();
151         List flags = new ArrayList();
152         for (int i = 0; i < patternArray.length; i++) {
153             Object[] o = getPattern(patternArray[i]);
154             patterns.add(o[0]);
155             substitutions.add(o[1]);
156             flags.add(o[2]);
157         }
158         
159         return new List[] {patterns, substitutions, flags};
160     }
161 
162     protected static List[] getPatternsFromStream(InputStream stream, String charset) throws MailetException, IOException {
163         List patterns = new ArrayList();
164         List substitutions = new ArrayList();
165         List flags = new ArrayList();
166         BufferedReader reader = new BufferedReader(charset != null ? new InputStreamReader(stream, charset) : new InputStreamReader(stream));
167         //BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("q:\\correzioniout"), "utf-8"));
168         
169         String line;
170         while ((line = reader.readLine()) != null) {
171             line = line.trim();
172             if (line.length() > 0 && !line.startsWith("#")) {
173                 if (line.length() < 2 && !line.startsWith("/") && !line.endsWith("/")) throw new MailetException("Invalid expression: " + line);
174                 Object[] o = getPattern(line.substring(1, line.length() - 1));
175                 patterns.add(o[0]);
176                 substitutions.add(o[1]);
177                 flags.add(o[2]);
178             }
179         }
180         reader.close();
181         return new List[] {patterns, substitutions, flags};
182     }
183     
184     /**
185      * @param filepar File path list (or resources if the path starts with #) comma separated
186      */
187     private List[] getPatternsFromFileList(String filepar) throws MailetException, IOException {
188         List patterns = new ArrayList();
189         List substitutions = new ArrayList();
190         List flags = new ArrayList();
191         String[] files = filepar.split(",");
192         for (int i = 0; i < files.length; i++) {
193             files[i] = files[i].trim();
194             if (debug > 0) log("Loading patterns from: " + files[i]);
195             String charset = null;
196             int pc = files[i].lastIndexOf('?');
197             if (pc >= 0) {
198                 charset = files[i].substring(pc + 1);
199                 files[i] = files[i].substring(0, pc);
200             }
201             InputStream is = null;
202             if (files[i].startsWith("#")) is = getClass().getResourceAsStream(files[i].substring(1));
203             else {
204                 File f = new File(files[i]);
205                 if (f.isFile()) is = new FileInputStream(f);
206             }
207             if (is != null) {
208                 List[] o = getPatternsFromStream(is, charset);
209                 patterns.addAll(o[0]);
210                 substitutions.addAll(o[1]);
211                 flags.addAll(o[2]);
212                 is.close();
213             }
214         }
215         return new List[] {patterns, substitutions, flags};
216     }
217     
218     protected static String applyPatterns(Pattern[] patterns, String[] substitutions, Integer[] pflags, String text, int debug, GenericMailet logOwner) {
219         for (int i = 0; i < patterns.length; i ++) {
220             int flags = pflags[i].intValue();
221             boolean changed = false;
222             do {
223                 changed = false;
224                 String replaced = patterns[i].matcher(text).replaceAll(substitutions[i]);
225                 if (!replaced.equals(text)) {
226                     if (debug > 0) logOwner.log("Subject rule match: " + patterns[i].pattern());
227                     text = replaced;
228                     changed = true;
229                 }
230             } while ((flags & FLAG_REPEAT) > 0 && changed);
231         }
232         
233         return text;
234     }
235     
236 
237     public void init() throws MailetException {
238         charset = getInitParameter(PARAMETER_NAME_CHARSET);
239         debug = Integer.parseInt(getInitParameter("debug", "0"));
240     }
241     
242     private ReplaceConfig initPatterns() throws MailetException {
243         try {
244             List bodyPatternsList = new ArrayList();
245             List bodySubstitutionsList = new ArrayList();
246             List bodyFlagsList = new ArrayList();
247             List subjectPatternsList = new ArrayList();
248             List subjectSubstitutionsList = new ArrayList();
249             List subjectFlagsList = new ArrayList();
250 
251             String pattern = getInitParameter(PARAMETER_NAME_SUBJECT_PATTERN);
252             if (pattern != null) {
253                 List[] o = getPatternsFromString(pattern);
254                 subjectPatternsList.addAll(o[0]);
255                 subjectSubstitutionsList.addAll(o[1]);
256                 subjectFlagsList.addAll(o[2]);
257             }
258             
259             pattern = getInitParameter(PARAMETER_NAME_BODY_PATTERN);
260             if (pattern != null) {
261                 List[] o = getPatternsFromString(pattern);
262                 bodyPatternsList.addAll(o[0]);
263                 bodySubstitutionsList.addAll(o[1]);
264                 bodyFlagsList.addAll(o[2]);
265             }
266             
267             String filepar = getInitParameter(PARAMETER_NAME_SUBJECT_PATTERNFILE);
268             if (filepar != null) {
269                 List[] o = getPatternsFromFileList(filepar);
270                 subjectPatternsList.addAll(o[0]);
271                 subjectSubstitutionsList.addAll(o[1]);
272                 subjectFlagsList.addAll(o[2]);
273             }
274         
275             filepar = getInitParameter(PARAMETER_NAME_BODY_PATTERNFILE);
276             if (filepar != null) {
277                 List[] o = getPatternsFromFileList(filepar);
278                 bodyPatternsList.addAll(o[0]);
279                 bodySubstitutionsList.addAll(o[1]);
280                 bodyFlagsList.addAll(o[2]);
281             }
282             
283             ReplaceConfig rConfig = new ReplaceConfig();
284             rConfig.subjectPatterns = (Pattern[]) subjectPatternsList.toArray(new Pattern[0]);
285             rConfig.subjectSubstitutions = (String[]) subjectSubstitutionsList.toArray(new String[0]);
286             rConfig.subjectFlags = (Integer[]) subjectFlagsList.toArray(new Integer[0]);
287             rConfig.bodyPatterns = (Pattern[]) bodyPatternsList.toArray(new Pattern[0]);
288             rConfig.bodySubstitutions = (String[]) bodySubstitutionsList.toArray(new String[0]);
289             rConfig.bodyFlags = (Integer[]) bodyFlagsList.toArray(new Integer[0]);
290             
291             return rConfig;
292             
293         } catch (FileNotFoundException e) {
294             throw new MailetException("Failed initialization", e);
295             
296         } catch (MailetException e) {
297             throw new MailetException("Failed initialization", e);
298             
299         } catch (IOException e) {
300             throw new MailetException("Failed initialization", e);
301             
302         }
303     }
304 
305     public void service(Mail mail) throws MailetException {
306         ReplaceConfig rConfig = initPatterns();
307         
308         try {
309             boolean mod = false;
310             boolean contentChanged = false;
311             
312             if (rConfig.subjectPatterns != null && rConfig.subjectPatterns.length > 0) {
313                 String subject = mail.getMessage().getSubject();
314                 if (subject == null) subject = "";
315                 subject = applyPatterns(rConfig.subjectPatterns, rConfig.subjectSubstitutions, rConfig.subjectFlags, subject, debug, this);
316                 if (charset != null) mail.getMessage().setSubject(subject, charset);
317                 else mail.getMessage().setSubject(subject);
318                 mod = true;
319             }
320             
321             if (rConfig.bodyPatterns != null && rConfig.bodyPatterns.length > 0) {
322                 Object bodyObj = mail.getMessage().getContent();
323                 if (bodyObj == null) bodyObj = "";
324                 if (bodyObj instanceof String) {
325                     String body = (String) bodyObj;
326                     body = applyPatterns(rConfig.bodyPatterns, rConfig.bodySubstitutions, rConfig.bodyFlags, body, debug, this);
327                     String contentType = mail.getMessage().getContentType();
328                     if (charset != null) {
329                         ContentType ct = new ContentType(contentType);
330                         ct.setParameter("charset", charset);
331                         contentType = ct.toString();
332                     }
333                     mail.getMessage().setContent(body, contentType);
334                     mod = true;
335                     contentChanged = true;
336                 }
337             }
338             
339             if (charset != null && !contentChanged) {
340                 ContentType ct = new ContentType(mail.getMessage().getContentType());
341                 ct.setParameter("charset", charset);
342                 String contentType = mail.getMessage().getContentType();
343                 mail.getMessage().setContent(mail.getMessage().getContent(), contentType);
344             }
345             
346             if (mod) mail.getMessage().saveChanges();
347             
348         } catch (MessagingException e) {
349             throw new MailetException("Error in replace", e);
350             
351         } catch (IOException e) {
352             throw new MailetException("Error in replace", e);
353         }
354     }
355 
356 }