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}