Source code for /engineering/autohit-2003/src/autohit/server/command/Command.javaOriginal file Command.java
   1 /**
   2  * AUTOHIT 2003
   3  * Copyright Erich P Gatejen (c) 1989,1997,2003,2004
   4  * 
   5  * This program is free software; you can redistribute it and/or modify 
   6  * it under the terms of the GNU General Public License as published by 
   7  * the Free Software Foundation; either version 2 of the License, or (at
   8  * your option) any later version.
   9  * This program is distributed in the hope that it will be useful, but
  10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12  * more details.
  13  * 
  14  * You should have received a copy of the GNU General Public License along
  15  * with this program; if not, write to the Free Software Foundation, Inc.,
  16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17  *
  18  * Additional license information can be found in the documentation.
  19  * @author Erich P Gatejen
  20  */
  21 package autohit.server.command;
  22 
  23 import java.util.Vector;
  24 import java.io.Serializable;
  25 
  26 import autohit.common.channels.Atom;
  27 import autohit.common.channels.Injector;
  28 import autohit.common.channels.Receipt;
  29 import autohit.server.ServerException;
  30 import autohit.server.SystemContext;
  31 import autohit.universe.Universe;
  32 import autohit.common.AutohitErrorCodes;
  33 import autohit.common.channels.ChannelException;
  34 
  35 /**
  36  * A command base class. All command inplementations should use extend it.
  37  * <p>
  38  * THIS IS NOT THREAD SAFE!!!! EVERY CommandServer should create their own
  39  * instances!!!! There are object fields that store the command parameters.
  40  * It's the cleanest way to do it. Cope and deal.
  41  * <p>
  42  * All exceptions should/will be caught and reported on the response channel
  43  * except if the command is poorly formed (it still will try to report) or is a
  44  * very serious problem.
  45  * <p>
  46  * <code>
  47  * Command sequence is as follows:
  48  * 1- Command class instantiation
  49  * 2- Get command parameters.  Kept in a vector.  Contstructed with
  50  * the helper static method createCommand()
  51  * 		0-UNI 		- (has default) Universe
  52  * 		1-RESPONSE 	- (has default) Injector for control response channel
  53  * 		2-TARGET	- (has default) Injector for target channel
  54  * 		3-CLASS		- Implementation class
  55  * 		4-COMMAND	- Command string
  56  * 		5-OBJECT	- Data object
  57  * 3- Call the command with call()
  58  * 		- Setup base
  59  * 4- Call verify in subclass()
  60  * 		- Subclass should call assert method in base for required/optional 
  61  *        for the parameters.
  62  * 		- The assert method will set the object fields.
  63  * 		- The base assert will throw an exception for any error.  It
  64  * 		  MUST be passed along.  The base will handle any reporting.
  65  * 5- Base will accept the command.
  66  * 6- Passed into chained execute()
  67  * 		- Problems should be thrown as exceptions
  68  * 7- Base will ack or nak the command.
  69  * 8- Return a receipt
  70  * </code>
  71  * 
  72  * @author Erich P. Gatejen
  73  * @version 1.0 <i>Version History</i><code>EPG - Initial - 24Jul03
  74  * EPG - Rewrite - 31Jul03
  75  * </code>
  76  */
  77 public abstract class Command implements Serializable {
  78 
  79 	/**
  80 	 * Constants */
  81 	public final static char RESPONSE_ELEMENT_SEPERATOR = '|';
  82 	public final static int UNI_LIST_INDEX = 0;
  83 	public final static int RESPONSE_LIST_INDEX = 1;
  84 	public final static int TARGET_LIST_INDEX = 2;
  85 	public final static int CLASS_LIST_INDEX = 3;
  86 	public final static int COMMAND_LIST_INDEX = 4;
  87 	public final static int OBJECT_LIST_INDEX = 5;
  88 
  89 	/**
  90 	 * Command objects. Don't change these! */
  91 	public Universe uni;
  92 	public Injector response;
  93 	public Injector target;
  94 	public String classobject;
  95 	public String command;
  96 	public Object data;
  97 	public int uniqueID;
  98 
  99 	private Vector commandlist;
 100 	protected SystemContext sc;
 101 
 102 	/**
 103 	 * Execute the command.
 104 	 * 
 105 	 * @throws ServerException
 106 	 * @return return the manreadable message for success.
 107 	 */
 108 	abstract public String execute() throws ServerException;
 109 
 110 	/**
 111 	 * Verify the command. Basically, it should just call the assert method
 112 	 * with the six parameters indicating if the fields are required or not
 113 	 * (optional). The first three (uni, target, and reponse) have defaults, if
 114 	 * nothing is passed. However, the the assert will throw an exception if it
 115 	 * is marked as required but isn't present.
 116 	 * <p>
 117 	 * The exception from accept should not be intercepted! The following is an
 118 	 * example implementation.
 119 	 * <p>
 120 	 * <code>
 121 	 * 
 122 	 * public String verify() throws ServerException {
 123 	 *   this.assert(true,true,false,false,false,true);
 124 	 * 	 return "parameters are good.";
 125 	 * }
 126 	 * </code>
 127 	 * 
 128 	 * @throws ServerException
 129 	 * @return return the manreadable message for accepting the command.
 130 	 */
 131 	abstract public String verify() throws ServerException;
 132 
 133 	/**
 134 	 * Get the textual name for the command.
 135 	 * 
 136 	 * @return return the manreadable name of this command.
 137 	 */
 138 	abstract public String getName();
 139 
 140 	/**
 141 	 * Execute the command. String Method.
 142 	 * <p>
 143 	 * it will throw a ServerException if the command is poorly formed and
 144 	 * cannot be accepted or there is a serious system problem. Outwise it
 145 	 * should just log issues to the response (or default) and return no
 146 	 * receipt.
 147 	 * 
 148 	 * @param c
 149 	 *           the System Context
 150 	 * @param cl
 151 	 *           the command list in a Vector
 152 	 * @throws ServerException
 153 	 * @return a receipt for the transaction (given by the responseChannel).
 154 	 *         This can be a log injector.
 155 	 */
 156 	public Receipt call(SystemContext c, Vector cl) throws ServerException {
 157 
 158 		// Setup
 159 		String victorydance = ".";
 160 		commandlist = cl;
 161 		sc = c;
 162 		Receipt rr = null;
 163 		uniqueID = sc.uniqueInteger();
 164 
 165 		// Catch any wild-assed exceptions
 166 		try {
 167 
 168 			// Verify the parameters
 169 			try {
 170 				this.verify();
 171 			} catch (ServerException se) {
 172 				// Abort the command. If the response channel is valid, send it
 173 				// there,
 174 				// otherwise use the default
 175 				if (response == null)
 176 					response = sc.getRootLogger().sinjector;
 177 				this.respond("malformed and aborted!", AutohitErrorCodes.EVENT_COMMAND_FAILED, null);
 178 				return null;
 179 			}
 180 
 181 			// Accept the command
 182 			this.respond("accepted.", AutohitErrorCodes.EVENT_COMMAND_ACCEPTED, null);
 183 
 184 			// Execute it
 185 			try {
 186 				String vic = this.execute();
 187 				if (vic != null)
 188 					victorydance = vic;
 189 
 190 			} catch (ServerException sse) {
 191 				// Failed command. nak and break out
 192 				this.respond("failed.  code[" + sse.numeric + "] " + sse.getMessage(), 
 193 						     AutohitErrorCodes.EVENT_COMMAND_FAILED, null);
 194 				return null;
 195 			}
 196 
 197 			// Issue the receipt
 198 			rr = new Receipt();
 199 			rr.setAsInteger(uniqueID);
 200 
 201 			// Ack the command
 202 			this.respond("succeeded.  Receipt issued.  " + victorydance,
 203 				         AutohitErrorCodes.EVENT_COMMAND_COMPLELTED, rr);
 204 
 205 		} catch (ChannelException ece) {
 206 			// This is pretty bad. If we can't use the channel, then the system
 207 			// state is dubious. What good is a command, if you can't give it
 208 			// a response? So PANIC!
 209 			throw new ServerException(
 210 				this.getMsgHeader()
 211 					+ "PANIC!  Could not use the responseChannel.  This makes it useless.  exception="
 212 					+ ece.getMessage(),
 213 				ServerException.CODE_SERVER_PANIC,
 214 				ece);
 215 		} catch (Exception e) {
 216 			// This should not have happened. Bad programmer should have
 217 			// trapped
 218 			// all exceptions before this!
 219 			throw new ServerException(
 220 				this.getMsgHeader()
 221 					+ "Software Detected Fault in Command.class.  Someone let an exception out.  File a bug.  exception="
 222 					+ e.getMessage(),
 223 				ServerException.CODE_SW_DETECTED_FAULT,
 224 				e);
 225 		}
 226 		return rr;
 227 	}
 228 
 229 	/**
 230 	 * Assert the parameters.
 231 	 * 
 232 	 * @param univ
 233 	 *           is 0-UNI required?
 234 	 * @param resp
 235 	 *           is 1-RESPONSE required?
 236 	 * @param targ
 237 	 *           is 2-TARGET required?
 238 	 * @param cobj
 239 	 *           is 3-CLASS required?
 240 	 * @param cmd
 241 	 *           is 4-COMMAND required?
 242 	 * @param dobj
 243 	 *           is 5-OBJECT required?
 244 	 * @throws ServerException.
 245 	 *            Do not intercept it!
 246 	 */
 247 	protected void assertparam(boolean univ, boolean resp, boolean targ, boolean cobj, boolean cmd, boolean dobj)
 248 		throws ServerException {
 249 
 250 		uni = null;
 251 		response = null;
 252 		target = null;
 253 		classobject = null;
 254 		command = null;
 255 		data = null;
 256 
 257 		try {
 258 			Object thang;
 259 
 260 			// 0-UNI
 261 			thang = commandlist.elementAt(UNI_LIST_INDEX);
 262 			if (thang == null) {
 263 				if (univ) {
 264 					throw new ServerException("0-UNI Universe is required", ServerException.CODE_COMMAND_ERROR);
 265 				} else {
 266 					uni = sc.getUniverse(); //default
 267 				}
 268 			} else if (!(thang instanceof Universe)) {
 269 				throw new ServerException(
 270 					"0-UNI Universe paramter is not a Universe",
 271 					ServerException.CODE_COMMAND_ERROR);
 272 			} else
 273 				uni = (Universe) thang; // accept passed
 274 
 275 			// 1-RESPONSE
 276 			thang = commandlist.elementAt(RESPONSE_LIST_INDEX);
 277 			if (thang == null) {
 278 				if (resp) {
 279 					throw new ServerException("1-RESPONSE Response is required", ServerException.CODE_COMMAND_ERROR);
 280 				} else {
 281 					response = sc.getRootLogger().sinjector; //default
 282 				}
 283 			} else if (!(thang instanceof Injector)) {
 284 				throw new ServerException(
 285 					"1-RESPONSE Response paramter is not an Injector",
 286 					ServerException.CODE_COMMAND_ERROR);
 287 			} else
 288 				response = (Injector) thang; // accept passed
 289 
 290 			// 2-TARGET
 291 			thang = commandlist.elementAt(TARGET_LIST_INDEX);
 292 			if (thang == null) {
 293 				if (targ) {
 294 					throw new ServerException("2-TARGET Response is required", ServerException.CODE_COMMAND_ERROR);
 295 				} else {
 296 					target = sc.getRootLogger().sinjector; //default
 297 				}
 298 			} else if (!(thang instanceof Injector)) {
 299 				throw new ServerException(
 300 					"2-TARGET Response paramter is not an Injector",
 301 					ServerException.CODE_COMMAND_ERROR);
 302 			} else
 303 				target = (Injector) thang; // accept passed
 304 
 305 			// 3-CLASS
 306 			if (cobj) {
 307 				thang = commandlist.elementAt(CLASS_LIST_INDEX);
 308 				if (thang == null) {
 309 					throw new ServerException("3-CLASS Class is required", ServerException.CODE_COMMAND_ERROR);
 310 				}
 311 				if (!(thang instanceof String)) {
 312 					throw new ServerException(
 313 						"3-CLASS Class paramter is not a String",
 314 						ServerException.CODE_COMMAND_ERROR);
 315 				}
 316 				classobject = (String) thang;
 317 			}
 318 
 319 			// 4-COMMAND
 320 			if (cmd) {
 321 				thang = commandlist.elementAt(COMMAND_LIST_INDEX);
 322 				if (thang == null) {
 323 					throw new ServerException("4-COMMAND Command is required", ServerException.CODE_COMMAND_ERROR);
 324 				}
 325 				if (!(thang instanceof String)) {
 326 					throw new ServerException(
 327 						"4-COMMAND Command paramter is not a String",
 328 						ServerException.CODE_COMMAND_ERROR);
 329 				}
 330 				command = (String) thang;
 331 			}
 332 
 333 			// 5-OBJECT
 334 			if (dobj) {
 335 				thang = commandlist.elementAt(OBJECT_LIST_INDEX);
 336 				if (thang == null) {
 337 					throw new ServerException("5-OBJECT Data Object is required", ServerException.CODE_COMMAND_ERROR);
 338 				}
 339 				if (!(thang instanceof Object)) {
 340 					throw new ServerException(
 341 						"5-OBJECT Data Object paramter is not an Object",
 342 						ServerException.CODE_COMMAND_ERROR);
 343 				}
 344 				data = (Object) thang;
 345 			}
 346 
 347 		} catch (ServerException se) {
 348 			throw se;
 349 		} catch (Exception e) {
 350 			throw new ServerException(
 351 				"Command list corrupt.  message=" + e.getMessage(),
 352 				ServerException.CODE_COMMAND_FAULT,
 353 				e);
 354 		}
 355 	}
 356 
 357 	// HELPER
 358 	public String getMsgHeader() {
 359 		return "CMD:" + this.getName() + " id[" + uniqueID + "] ";
 360 	}
 361 
 362 	/**
 363 	 * Send response.  The response MUST be set.
 364 	 * 
 365 	 * @param info
 366 	 *           text of the response
 367 	 * @param code
 368 	 *           code of the response. Usually an EVENT
 369 	 * @param rr
 370 	 *           Receipt for the command. May be null, if none was issued.
 371 	 */
 372 	public void respond(String info, int code, Receipt rr) throws Exception {
 373 		response.post(new CommandResponseAtom(code, this.getMsgHeader() + info, Atom.ROUTINE, uniqueID, rr));
 374 	}
 375 
 376 	/**
 377 	 * Send target.   The target MUST be set.
 378 	 * 
 379 	 * @param info
 380 	 *           text of the response
 381 	 * @param code
 382 	 *           code of the response. Usually an EVENT
 383 	 * @param rr
 384 	 *           Receipt for the command. May be null, if none was issued.
 385 	 */
 386 	public void sendTarget(String info, int code, Receipt rr) throws Exception {
 387 		target.post(new CommandResponseAtom(code, this.getMsgHeader() + info, Atom.ROUTINE, uniqueID, rr));
 388 	}
 389 
 390 	/**
 391 	 * Create a command list. It is a very good object to make all passed items
 392 	 * Serializable. You cannot predict how the command will be issued. you can
 393 	 * pass null for any parameter you don't want to pass.
 394 	 * 
 395 	 * @param univ
 396 	 *           Specify a universe object?
 397 	 * @param resp
 398 	 *           Specify a response Injector?
 399 	 * @param targ
 400 	 *           Specify a target Injector?
 401 	 * @param cobj
 402 	 *           Specify a command Class implementation (string name)?
 403 	 * @param cmd
 404 	 *           Specify a command string?
 405 	 * @param dobj
 406 	 *           Specify a data object?
 407 	 * @return a Vector representing the command.
 408 	 */
 409 	static public Vector createCommand(
 410 		Universe univ,
 411 		Injector resp,
 412 		Injector targ,
 413 		String cobj,
 414 		String cmd,
 415 		Object dobj) {
 416 
 417 		Vector cmdList = new Vector(OBJECT_LIST_INDEX + 1);
 418 		cmdList.setSize(OBJECT_LIST_INDEX + 1);
 419 		cmdList.set(UNI_LIST_INDEX, univ);
 420 		cmdList.set(RESPONSE_LIST_INDEX, resp);
 421 		cmdList.set(TARGET_LIST_INDEX, targ);
 422 		cmdList.set(CLASS_LIST_INDEX, cobj);
 423 		cmdList.set(COMMAND_LIST_INDEX, cmd);
 424 		cmdList.set(OBJECT_LIST_INDEX, dobj);
 425 
 426 		return cmdList;
 427 	}
 428 
 429 }