001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019
020package org.apache.james.mpt.ant;
021
022import java.io.File;
023import java.io.FileReader;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.Reader;
027import java.io.StringReader;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.Iterator;
031
032import org.apache.james.mpt.Runner;
033import org.apache.james.mpt.api.ImapFeatures;
034import org.apache.james.mpt.api.ImapFeatures.Feature;
035import org.apache.james.mpt.api.Monitor;
036import org.apache.james.mpt.host.ExternalHostSystem;
037import org.apache.james.mpt.protocol.ProtocolSessionBuilder;
038import org.apache.james.mpt.user.ScriptedUserAdder;
039import org.apache.tools.ant.BuildException;
040import org.apache.tools.ant.Project;
041import org.apache.tools.ant.Task;
042import org.apache.tools.ant.types.Resource;
043import org.apache.tools.ant.types.ResourceCollection;
044import org.apache.tools.ant.types.resources.FileResource;
045import org.apache.tools.ant.types.resources.Union;
046
047/**
048 * Task executes MPT scripts against a server
049 * running on a given port and host.
050 */
051public class MailProtocolTestTask extends Task implements Monitor {
052
053    private static final ImapFeatures SUPPORTED_FEATURES = ImapFeatures.of(Feature.NAMESPACE_SUPPORT);
054    
055    private boolean quiet = false;
056    private File script;
057    private Union scripts;
058    private int port = 0;
059    private String host = "127.0.0.1";
060    private boolean skip = false;
061    private String shabang = null;
062    private final Collection<AddUser> users = new ArrayList<AddUser>();
063    private String errorProperty;
064    
065    /**
066     * Gets the error property.
067     * 
068     * @return name of the ant property to be set on error,
069     * null if the script should terminate on error
070     */
071    public String getErrorProperty() {
072        return errorProperty;
073    }
074
075    /**
076     * Sets the error property.
077     * @param errorProperty name of the ant property to be set on error,
078     * nul if the script should terminate on error
079     */
080    public void setErrorProperty(String errorProperty) {
081        this.errorProperty = errorProperty;
082    }
083
084    /**
085     * Should progress output be suppressed?
086     * @return true if progress information should be suppressed,
087     * false otherwise
088     */
089    public boolean isQuiet() {
090        return quiet;
091    }
092
093    /**
094     * Sets whether progress output should be suppressed/
095     * @param quiet true if progress information should be suppressed,
096     * false otherwise
097     */
098    public void setQuiet(boolean quiet) {
099        this.quiet = quiet;
100    }
101
102    /**
103     * Should the execution be skipped?
104     * @return true if exection should be skipped, 
105     * otherwise false
106     */
107    public boolean isSkip() {
108        return skip;
109    }
110
111    /**
112     * Sets execution skipping.
113     * @param skip true to skip excution
114     */
115    public void setSkip(boolean skip) {
116        this.skip = skip;
117    }
118
119    /**
120     * Gets the host (either name or number) against which this
121     * test will run.
122     * @return host, not null
123     */
124    public String getHost() {
125        return host;
126    }
127
128    /**
129     * Sets the host (either name or number) against which this
130     * test will run.
131     * @param host not null
132     */
133    public void setHost(String host) {
134        this.host = host;
135    }
136
137    /**
138     * Gets the port against which this test will run.
139     * @return port number
140     */
141    public int getPort() {
142        return port;
143    }
144
145    /**
146     * Sets the port aginst which this test will run.
147     * @param port port number
148     */
149    public void setPort(int port) {
150        this.port = port;
151    }
152
153    /**
154     * Gets the script to execute.
155     * @return file containing test script
156     */
157    public File getScript() {
158        return script;
159    }
160
161    /**
162     * Sets the script to execute.
163     * @param script not null
164     */
165    public void setScript(File script) {
166        this.script = script;
167    }
168
169    /**
170     * Gets script shabang.
171     * This will be substituted for the first server response.
172     * @return script shabang, 
173     * or null for no shabang
174     */
175    public String getShabang() {
176        return shabang;
177    }
178    
179    /**
180     * Sets the script shabang.
181     * When not null, this value will be used to be substituted for the 
182     * first server response.
183     * @param shabang script shabang, 
184     * or null for no shabang.
185     */
186    public void setShabang(String shabang) {
187        this.shabang = shabang;
188    }
189
190    @Override
191    public void execute() throws BuildException {
192        if (port <= 0) {
193            throw new BuildException("Port must be set to a positive integer");
194        }
195        
196        if (scripts == null && script == null) {
197            throw new BuildException("Scripts must be specified as an embedded resource collection"); 
198        }
199        
200        if (scripts != null && script != null) {
201            throw new BuildException("Scripts can be specified either by the script attribute or as resource collections but not both."); 
202        }
203        
204        for(AddUser user: users) {
205            user.validate();
206        }
207        
208        if(skip) {
209            log("Skipping excution");
210        } else if (errorProperty == null) {
211            doExecute();
212        } else {
213            try {
214                doExecute();
215            } catch (BuildException e) {
216                final Project project = getProject();
217                project.setProperty(errorProperty, e.getMessage());
218                log(e, Project.MSG_DEBUG);
219            }
220        }
221    }
222
223    public void add(ResourceCollection resources) {
224        if (scripts == null) {
225            scripts = new Union();
226        }
227        scripts.add(resources);
228    }
229    
230    private void doExecute() throws BuildException {
231        for (AddUser userAdder: users) {
232            userAdder.execute();
233        }
234        
235        final ExternalHostSystem host = new ExternalHostSystem(SUPPORTED_FEATURES, getHost(), getPort(), this, getShabang(), null);
236        final ProtocolSessionBuilder builder = new ProtocolSessionBuilder();
237        
238        if (scripts == null) {
239            scripts = new Union();
240            scripts.add(new FileResource(script));
241        }
242        
243        for (Iterator<?> it = scripts.iterator(); it.hasNext();) {
244            final Resource resource = (Resource) it.next();
245            try {
246                final Runner runner = new Runner();
247                
248                try {
249                    
250                    final InputStream inputStream = resource.getInputStream();
251                    final String name = resource.getName();
252                    builder.addProtocolLines(name == null ? "[Unknown]" : name, inputStream, runner.getTestElements());
253                    runner.runSessions(host);
254                    
255                } catch (UnsupportedOperationException e) {
256                    log("Resource cannot be read: " + resource.getName(), Project.MSG_WARN);
257                }
258            } catch (IOException e) {
259                throw new BuildException("Cannot load script " + resource.getName(), e);
260            } catch (Exception e) {
261                log(e.getMessage(), Project.MSG_ERR);
262                throw new BuildException("[FAILURE] in script " + resource.getName() + "\n" + e.getMessage(), e);
263            }
264            
265        }
266    
267    }
268    
269    public AddUser createAddUser() {
270        final AddUser result = new AddUser();
271        users.add(result);
272        return result;
273    }
274
275    /**
276     * Adds a user.
277     */
278    public class AddUser {
279        
280        private int port;
281        private String user;
282        private String passwd;
283        private File script;
284        private String scriptText;
285
286        /**
287         * Gets the port against which the user addition
288         * script should be executed.
289         * @return port number
290         */
291        public int getPort() {
292            return port;
293        }
294
295        /**
296         * Sets the port against which the user addition
297         * script should be executed.
298         * @param port port number
299         */
300        public void setPort(int port) {
301            this.port = port;
302        }
303
304        /**
305         * Gets the password for the user.
306         * @return password not null
307         */
308        public String getPasswd() {
309            return passwd;
310        }
311
312        /**
313         * Sets the password for the user.
314         * This will be passed in the user creation script.
315         * @param passwd not null
316         */
317        public void setPasswd(String passwd) {
318            this.passwd = passwd;
319        }
320
321        /**
322         * Gets the name of the user to be created.
323         * @return user name, not null
324         */
325        public String getUser() {
326            return user;
327        }
328
329        /**
330         * Sets the name of the user to be created.
331         * @param user not null
332         */
333        public void setUser(String user) {
334            this.user = user;
335        }
336        
337        /**
338         * Sets user addition script.
339         * @param scriptText not null
340         */
341        public void addText(String scriptText) {
342            this.scriptText = getProject().replaceProperties(scriptText);
343        }
344
345        /**
346         * Gets the file containing the user creation script.
347         * @return not null
348         */
349        public File getScript() {
350            return script;
351        }
352
353        /**
354         * Sets the file containing the user creation script.
355         * @param script not null
356         */
357        public void setScript(File script) {
358            this.script = script;
359        }
360        
361        /**
362         * Validates mandatory fields have been filled.
363         */
364        void validate() throws BuildException {
365            if (script == null && scriptText == null) {
366                throw new BuildException("Either the 'script' attribute must be set, or the body must contain the text of the script");
367            }
368            
369            if (script != null && scriptText != null) {
370                throw new BuildException("Choose either script text or script attribute but not both.");
371            }
372            
373            if (port <= 0) {
374                throw new BuildException("'port' attribute must be set on AddUser to the port against which the script should run.");
375            }
376        }
377        
378        /**
379         * Creates a user.
380         * @throws BuildException
381         */
382        void execute() throws BuildException {
383            validate();
384            try {
385                final File scriptFile = getScript();
386                final Reader reader;
387                if (scriptFile == null) {
388                    reader = new StringReader(scriptText);
389                } else {
390                    reader = new FileReader(scriptFile);
391                }
392                final ScriptedUserAdder adder = new ScriptedUserAdder(getHost(), getPort(), MailProtocolTestTask.this);
393                adder.addUser(getUser(), getPasswd(), reader);
394            } catch (Exception e) {
395                log(e.getMessage(), Project.MSG_ERR);
396                throw new BuildException("User addition failed: \n" + e.getMessage(), e);
397            }
398        } 
399    }
400
401    public void note(String message) {
402        if (quiet) {
403            log(message, Project.MSG_DEBUG);
404        } else {
405            log(message, Project.MSG_INFO);
406        }
407    }
408
409    public void debug(char character) {
410        log("'" + character + "'", Project.MSG_DEBUG);
411    }
412
413    public void debug(String message) {
414        log(message, Project.MSG_DEBUG);
415    }
416}