001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.scxml.model; 018 019 import java.util.ArrayList; 020 import java.util.Collection; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.StringTokenizer; 025 026 import org.apache.commons.logging.Log; 027 import org.apache.commons.scxml.Context; 028 import org.apache.commons.scxml.ErrorReporter; 029 import org.apache.commons.scxml.Evaluator; 030 import org.apache.commons.scxml.EventDispatcher; 031 import org.apache.commons.scxml.SCInstance; 032 import org.apache.commons.scxml.SCXMLExpressionException; 033 import org.apache.commons.scxml.SCXMLHelper; 034 import org.apache.commons.scxml.TriggerEvent; 035 import org.apache.commons.scxml.semantics.ErrorConstants; 036 037 /** 038 * The class in this SCXML object model that corresponds to the 039 * <send> SCXML element. 040 * 041 */ 042 public class Send extends Action implements ExternalContent { 043 044 /** 045 * Serial version UID. 046 */ 047 private static final long serialVersionUID = 1L; 048 049 /** 050 * The default targettype. 051 */ 052 private static final String TARGETTYPE_SCXML = "scxml"; 053 054 /** 055 * The spec mandated derived event when target cannot be reached 056 * for TARGETTYPE_SCXML. 057 */ 058 private static final String EVENT_ERR_SEND_TARGETUNAVAILABLE = 059 "error.send.targetunavailable"; 060 061 /** 062 * The ID of the send message. 063 */ 064 private String sendid; 065 066 /** 067 * An expression returning the target location of the event. 068 */ 069 private String target; 070 071 /** 072 * The type of the Event I/O Processor that the event. 073 * should be dispatched to 074 */ 075 private String targettype; 076 077 /** 078 * The event is dispatched after the delay interval elapses. 079 */ 080 private String delay; 081 082 /** 083 * The data containing information which may be used by the 084 * implementing platform to configure the event processor. 085 */ 086 private String hints; 087 088 /** 089 * The namelist to the sent. 090 */ 091 private String namelist; 092 093 /** 094 * The list of external nodes associated with this <send> element. 095 */ 096 private List externalNodes; 097 098 /** 099 * The type of event being generated. 100 */ 101 private String event; 102 103 /** 104 * OutputFormat used to serialize external nodes. 105 * 106 private static final OutputFormat format; 107 static { 108 format = new OutputFormat(); 109 format.setOmitXMLDeclaration(true); 110 } 111 */ 112 113 /** 114 * Constructor. 115 */ 116 public Send() { 117 super(); 118 this.externalNodes = new ArrayList(); 119 } 120 121 /** 122 * Get the delay. 123 * 124 * @return Returns the delay. 125 */ 126 public final String getDelay() { 127 return delay; 128 } 129 130 /** 131 * Set the delay. 132 * 133 * @param delay The delay to set. 134 */ 135 public final void setDelay(final String delay) { 136 this.delay = delay; 137 } 138 139 /** 140 * Get the list of external namespaced child nodes. 141 * 142 * @return List Returns the list of externalnodes. 143 */ 144 public final List getExternalNodes() { 145 return externalNodes; 146 } 147 148 /** 149 * Set the list of external namespaced child nodes. 150 * 151 * @param externalNodes The externalnode to set. 152 */ 153 public final void setExternalNodes(final List externalNodes) { 154 this.externalNodes = externalNodes; 155 } 156 157 /** 158 * Get the hints for this <send> element. 159 * 160 * @return String Returns the hints. 161 */ 162 public final String getHints() { 163 return hints; 164 } 165 166 /** 167 * Set the hints for this <send> element. 168 * 169 * @param hints The hints to set. 170 */ 171 public final void setHints(final String hints) { 172 this.hints = hints; 173 } 174 175 /** 176 * Get the namelist. 177 * 178 * @return String Returns the namelist. 179 */ 180 public final String getNamelist() { 181 return namelist; 182 } 183 184 /** 185 * Set the namelist. 186 * 187 * @param namelist The namelist to set. 188 */ 189 public final void setNamelist(final String namelist) { 190 this.namelist = namelist; 191 } 192 193 /** 194 * Get the identifier for this <send> element. 195 * 196 * @return String Returns the sendid. 197 */ 198 public final String getSendid() { 199 return sendid; 200 } 201 202 /** 203 * Set the identifier for this <send> element. 204 * 205 * @param sendid The sendid to set. 206 */ 207 public final void setSendid(final String sendid) { 208 this.sendid = sendid; 209 } 210 211 /** 212 * Get the target for this <send> element. 213 * 214 * @return String Returns the target. 215 */ 216 public final String getTarget() { 217 return target; 218 } 219 220 /** 221 * Set the target for this <send> element. 222 * 223 * @param target The target to set. 224 */ 225 public final void setTarget(final String target) { 226 this.target = target; 227 } 228 229 /** 230 * Get the target type for this <send> element. 231 * 232 * @return String Returns the targettype. 233 */ 234 public final String getTargettype() { 235 return targettype; 236 } 237 238 /** 239 * Set the target type for this <send> element. 240 * 241 * @param targettype The targettype to set. 242 */ 243 public final void setTargettype(final String targettype) { 244 this.targettype = targettype; 245 } 246 247 /** 248 * Get the event to send. 249 * 250 * @param event The event to set. 251 */ 252 public final void setEvent(final String event) { 253 this.event = event; 254 } 255 256 /** 257 * Set the event to send. 258 * 259 * @return String Returns the event. 260 */ 261 public final String getEvent() { 262 return event; 263 } 264 265 /** 266 * {@inheritDoc} 267 */ 268 public void execute(final EventDispatcher evtDispatcher, 269 final ErrorReporter errRep, final SCInstance scInstance, 270 final Log appLog, final Collection derivedEvents) 271 throws ModelException, SCXMLExpressionException { 272 // Send attributes evaluation 273 TransitionTarget parentTarget = getParentTransitionTarget(); 274 Context ctx = scInstance.getContext(parentTarget); 275 ctx.setLocal(getNamespacesKey(), getNamespaces()); 276 Evaluator eval = scInstance.getEvaluator(); 277 // Most attributes of <send> are expressions so need to be 278 // evaluated before the EventDispatcher callback 279 Object hintsValue = null; 280 if (!SCXMLHelper.isStringEmpty(hints)) { 281 hintsValue = eval.eval(ctx, hints); 282 } 283 String targetValue = target; 284 if (!SCXMLHelper.isStringEmpty(target)) { 285 targetValue = (String) eval.eval(ctx, target); 286 if (SCXMLHelper.isStringEmpty(targetValue) 287 && appLog.isWarnEnabled()) { 288 appLog.warn("<send>: target expression \"" + target 289 + "\" evaluated to null or empty String"); 290 } 291 } 292 String targettypeValue = targettype; 293 if (!SCXMLHelper.isStringEmpty(targettype)) { 294 targettypeValue = (String) eval.eval(ctx, targettype); 295 if (SCXMLHelper.isStringEmpty(targettypeValue) 296 && appLog.isWarnEnabled()) { 297 appLog.warn("<send>: targettype expression \"" + targettype 298 + "\" evaluated to null or empty String"); 299 } 300 } else { 301 // must default to 'scxml' when unspecified 302 targettypeValue = TARGETTYPE_SCXML; 303 } 304 Map params = null; 305 if (!SCXMLHelper.isStringEmpty(namelist)) { 306 StringTokenizer tkn = new StringTokenizer(namelist); 307 params = new HashMap(tkn.countTokens()); 308 while (tkn.hasMoreTokens()) { 309 String varName = tkn.nextToken(); 310 Object varObj = ctx.get(varName); 311 if (varObj == null) { 312 //considered as a warning here 313 errRep.onError(ErrorConstants.UNDEFINED_VARIABLE, 314 varName + " = null", parentTarget); 315 } 316 params.put(varName, varObj); 317 } 318 } 319 long wait = 0L; 320 if (!SCXMLHelper.isStringEmpty(delay)) { 321 Object delayValue = eval.eval(ctx, delay); 322 if (delayValue != null) { 323 String delayString = delayValue.toString(); 324 wait = parseDelay(delayString, appLog); 325 } 326 } 327 String eventValue = event; 328 if (!SCXMLHelper.isStringEmpty(event)) { 329 eventValue = (String) eval.eval(ctx, event); 330 if (SCXMLHelper.isStringEmpty(eventValue) 331 && appLog.isWarnEnabled()) { 332 appLog.warn("<send>: event expression \"" + event 333 + "\" evaluated to null or empty String"); 334 } 335 } 336 // Lets see if we should handle it ourselves 337 if (targettypeValue != null 338 && targettypeValue.trim().equalsIgnoreCase(TARGETTYPE_SCXML)) { 339 if (SCXMLHelper.isStringEmpty(targetValue)) { 340 // TODO: Remove both short-circuit passes in v1.0 341 if (wait == 0L) { 342 if (appLog.isDebugEnabled()) { 343 appLog.debug("<send>: Enqueued event '" + eventValue 344 + "' with no delay"); 345 } 346 derivedEvents.add(new TriggerEvent(eventValue, 347 TriggerEvent.SIGNAL_EVENT, params)); 348 return; 349 } 350 } else { 351 // We know of no other 352 if (appLog.isWarnEnabled()) { 353 appLog.warn("<send>: Unavailable target - " 354 + targetValue); 355 } 356 derivedEvents.add(new TriggerEvent( 357 EVENT_ERR_SEND_TARGETUNAVAILABLE, 358 TriggerEvent.ERROR_EVENT)); 359 // short-circuit the EventDispatcher 360 return; 361 } 362 } 363 ctx.setLocal(getNamespacesKey(), null); 364 if (appLog.isDebugEnabled()) { 365 appLog.debug("<send>: Dispatching event '" + eventValue 366 + "' to target '" + targetValue + "' of target type '" 367 + targettypeValue + "' with suggested delay of " + wait 368 + "ms"); 369 } 370 // Else, let the EventDispatcher take care of it 371 evtDispatcher.send(sendid, targetValue, targettypeValue, eventValue, 372 params, hintsValue, wait, externalNodes); 373 } 374 375 /** 376 * Parse delay. 377 * 378 * @param delayString The String value of the delay, in CSS2 format 379 * @param appLog The application log 380 * @return The parsed delay in milliseconds 381 * @throws SCXMLExpressionException If the delay cannot be parsed 382 */ 383 private long parseDelay(final String delayString, final Log appLog) 384 throws SCXMLExpressionException { 385 386 long wait = 0L; 387 long multiplier = 1L; 388 389 if (!SCXMLHelper.isStringEmpty(delayString)) { 390 391 String trimDelay = delayString.trim(); 392 String numericDelay = trimDelay; 393 if (trimDelay.endsWith(MILLIS)) { 394 numericDelay = trimDelay.substring(0, trimDelay.length() - 2); 395 } else if (trimDelay.endsWith(SECONDS)) { 396 multiplier = MILLIS_IN_A_SECOND; 397 numericDelay = trimDelay.substring(0, trimDelay.length() - 1); 398 } else if (trimDelay.endsWith(MINUTES)) { // Not CSS2 399 multiplier = MILLIS_IN_A_MINUTE; 400 numericDelay = trimDelay.substring(0, trimDelay.length() - 1); 401 } 402 403 try { 404 wait = Long.parseLong(numericDelay); 405 } catch (NumberFormatException nfe) { 406 appLog.error(nfe.getMessage(), nfe); 407 throw new SCXMLExpressionException(nfe.getMessage(), nfe); 408 } 409 wait *= multiplier; 410 411 } 412 413 return wait; 414 415 } 416 417 /** The suffix in the delay string for milliseconds. */ 418 private static final String MILLIS = "ms"; 419 420 /** The suffix in the delay string for seconds. */ 421 private static final String SECONDS = "s"; 422 423 /** The suffix in the delay string for minutes. */ 424 private static final String MINUTES = "m"; 425 426 /** The number of milliseconds in a second. */ 427 private static final long MILLIS_IN_A_SECOND = 1000L; 428 429 /** The number of milliseconds in a minute. */ 430 private static final long MILLIS_IN_A_MINUTE = 60000L; 431 432 } 433