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 */
017package org.apache.commons.scxml2.model;
018
019import java.util.LinkedHashMap;
020import java.util.Map;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.scxml2.ActionExecutionContext;
024import org.apache.commons.scxml2.Context;
025import org.apache.commons.scxml2.Evaluator;
026import org.apache.commons.scxml2.SCXMLExpressionException;
027import org.apache.commons.scxml2.SCXMLIOProcessor;
028import org.apache.commons.scxml2.SCXMLSystemContext;
029
030/**
031 * The class in this SCXML object model that corresponds to the
032 * <send> SCXML element.
033 *
034 */
035public class Send extends NamelistHolder implements ContentContainer {
036
037    /**
038     * Serial version UID.
039     */
040    private static final long serialVersionUID = 1L;
041
042    /**
043     * The suffix in the delay string for milliseconds.
044     */
045    private static final String MILLIS = "ms";
046
047    /**
048     * The suffix in the delay string for seconds.
049     */
050    private static final String SECONDS = "s";
051
052    /**
053     * The suffix in the delay string for minutes.
054     */
055    private static final String MINUTES = "m";
056
057    /**
058     * The number of milliseconds in a second.
059     */
060    private static final long MILLIS_IN_A_SECOND = 1000L;
061
062    /**
063     * The number of milliseconds in a minute.
064     */
065    private static final long MILLIS_IN_A_MINUTE = 60000L;
066
067    /**
068     * The ID of the send message.
069     */
070    private String id;
071
072    /**
073     * Path expression evaluating to a location within a previously defined XML data tree.
074     */
075    private String idlocation;
076
077    /**
078     * The target location of the event.
079     */
080    private String target;
081
082
083    /**
084     * An expression specifying the target location of the event.
085     */
086    private String targetexpr;
087
088    /**
089     * The type of the Event I/O Processor that the event should be dispatched to.
090     */
091    private String type;
092
093    /**
094     * An expression defining the type of the Event I/O Processor that the event should be dispatched to.
095     */
096    private String typeexpr;
097
098    /**
099     * The delay the event is dispatched after.
100     */
101    private String delay;
102
103    /**
104     * An expression defining the delay the event is dispatched after.
105     */
106    private String delayexpr;
107
108    /**
109     * The data containing information which may be used by the implementing platform to configure the event processor.
110     */
111    private String hints;
112
113    /**
114     * The type of event being generated.
115     */
116    private String event;
117
118    /**
119     * An expression defining the type of event being generated.
120     */
121    private String eventexpr;
122
123    /**
124     * The <content/> of this send
125     */
126    private Content content;
127
128    /**
129     * Constructor.
130     */
131    public Send() {
132        super();
133    }
134
135    /**
136     * @return the idlocation
137     */
138    public String getIdlocation() {
139        return idlocation;
140    }
141
142    /**
143     * Set the idlocation expression
144     *
145     * @param idlocation The idlocation expression
146     */
147    public void setIdlocation(final String idlocation) {
148        this.idlocation = idlocation;
149    }
150
151    /**
152     * Get the delay.
153     *
154     * @return Returns the delay.
155     */
156    public final String getDelay() {
157        return delay;
158    }
159
160    /**
161     * Set the delay.
162     *
163     * @param delay The delay to set.
164     */
165    public final void setDelay(final String delay) {
166        this.delay = delay;
167    }
168
169    /**
170     * @return The delay expression
171     */
172    public String getDelayexpr() {
173        return delayexpr;
174    }
175
176    /**
177     * Set the delay expression
178     *
179     * @param delayexpr The delay expression to set
180     */
181    public void setDelayexpr(final String delayexpr) {
182        this.delayexpr = delayexpr;
183    }
184
185    /**
186     * Get the hints for this <send> element.
187     *
188     * @return String Returns the hints.
189     */
190    public final String getHints() {
191        return hints;
192    }
193
194    /**
195     * Set the hints for this <send> element.
196     *
197     * @param hints The hints to set.
198     */
199    public final void setHints(final String hints) {
200        this.hints = hints;
201    }
202
203    /**
204     * Get the identifier for this <send> element.
205     *
206     * @return String Returns the id.
207     */
208    public final String getId() {
209        return id;
210    }
211
212    /**
213     * Set the identifier for this <send> element.
214     *
215     * @param id The id to set.
216     */
217    public final void setId(final String id) {
218        this.id = id;
219    }
220
221    /**
222     * Get the target for this <send> element.
223     *
224     * @return String Returns the target.
225     */
226    public final String getTarget() {
227        return target;
228    }
229
230    /**
231     * Set the target for this <send> element.
232     *
233     * @param target The target to set.
234     */
235    public final void setTarget(final String target) {
236        this.target = target;
237    }
238
239    /**
240     * @return The target expression
241     */
242    public String getTargetexpr() {
243        return targetexpr;
244    }
245
246    /**
247     * Set the target expression
248     *
249     * @param targetexpr The target expression to set
250     */
251    public void setTargetexpr(final String targetexpr) {
252        this.targetexpr = targetexpr;
253    }
254
255    /**
256     * Get the type for this <send> element.
257     *
258     * @return String Returns the type.
259     */
260    public final String getType() {
261        return type;
262    }
263
264    /**
265     * Set the type for this <send> element.
266     *
267     * @param type The type to set.
268     */
269    public final void setType(final String type) {
270        this.type = type;
271    }
272
273    /**
274     * @return The type expression
275     */
276    public String getTypeexpr() {
277        return typeexpr;
278    }
279
280    /**
281     * Sets the type expression
282     *
283     * @param typeexpr The type expression to set
284     */
285    public void setTypeexpr(final String typeexpr) {
286        this.typeexpr = typeexpr;
287    }
288
289    /**
290     * Get the event to send.
291     *
292     * @param event The event to set.
293     */
294    public final void setEvent(final String event) {
295        this.event = event;
296    }
297
298    /**
299     * Set the event to send.
300     *
301     * @return String Returns the event.
302     */
303    public final String getEvent() {
304        return event;
305    }
306
307    /**
308     * @return The event expression
309     */
310    public String getEventexpr() {
311        return eventexpr;
312    }
313
314    /**
315     * Sets the event expression
316     *
317     * @param eventexpr The event expression to set
318     */
319    public void setEventexpr(final String eventexpr) {
320        this.eventexpr = eventexpr;
321    }
322
323    /**
324     * Returns the content
325     *
326     * @return the content
327     */
328    public Content getContent() {
329        return content;
330    }
331
332    /**
333     * Sets the content
334     *
335     * @param content the content to set
336     */
337    public void setContent(final Content content) {
338        this.content = content;
339    }
340
341    /**
342     * {@inheritDoc}
343     */
344    @SuppressWarnings("unchecked")
345    @Override
346    public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
347        // Send attributes evaluation
348        EnterableState parentState = getParentEnterableState();
349        Context ctx = exctx.getContext(parentState);
350        ctx.setLocal(getNamespacesKey(), getNamespaces());
351        Evaluator eval = exctx.getEvaluator();
352        // Most attributes of <send> are expressions so need to be
353        // evaluated before the EventDispatcher callback
354        Object hintsValue = null;
355        if (hints != null) {
356            hintsValue = eval.eval(ctx, hints);
357        }
358        if (id == null) {
359            id = ctx.getSystemContext().generateSessionId();
360            if (idlocation != null) {
361                eval.evalAssign(ctx, idlocation, id, Evaluator.AssignType.REPLACE_CHILDREN, null);
362            }
363        }
364        String targetValue = target;
365        if (targetValue == null && targetexpr != null) {
366            targetValue = (String) getTextContentIfNodeResult(eval.eval(ctx, targetexpr));
367            if ((targetValue == null || targetValue.trim().length() == 0)
368                    && exctx.getAppLog().isWarnEnabled()) {
369                exctx.getAppLog().warn("<send>: target expression \"" + targetexpr
370                        + "\" evaluated to null or empty String");
371            }
372        }
373        String typeValue = type;
374        if (typeValue == null && typeexpr != null) {
375            typeValue = (String) getTextContentIfNodeResult(eval.eval(ctx, typeexpr));
376            if ((typeValue == null || typeValue.trim().length() == 0)
377                    && exctx.getAppLog().isWarnEnabled()) {
378                exctx.getAppLog().warn("<send>: type expression \"" + typeexpr
379                        + "\" evaluated to null or empty String");
380            }
381        }
382        if (typeValue == null) {
383            // must default to 'scxml' when unspecified
384            typeValue = SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR;
385        } else if (!SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR.equals(typeValue) && typeValue.trim().equalsIgnoreCase(SCXMLIOProcessor.SCXML_EVENT_PROCESSOR)) {
386            typeValue = SCXMLIOProcessor.DEFAULT_EVENT_PROCESSOR;
387        }
388        Object payload = null;
389        Map<String, Object> payloadDataMap = new LinkedHashMap<String, Object>();
390        addNamelistDataToPayload(exctx, payloadDataMap);
391        addParamsToPayload(exctx, payloadDataMap);
392        if (!payloadDataMap.isEmpty()) {
393            payload = makeEventPayload(eval, payloadDataMap);
394        }
395        else if (content != null) {
396            if (content.getExpr() != null) {
397                payload = clonePayloadValue(eval.eval(ctx, content.getExpr()));
398            } else {
399                payload = clonePayloadValue(content.getBody());
400            }
401        }
402        long wait = 0L;
403        String delayString = delay;
404        if (delayString == null && delayexpr != null) {
405            Object delayValue = getTextContentIfNodeResult(eval.eval(ctx, delayexpr));
406            if (delayValue != null) {
407                delayString = delayValue.toString();
408            }
409        }
410        if (delayString != null) {
411            wait = parseDelay(delayString, exctx.getAppLog());
412        }
413        String eventValue = event;
414        if (eventValue == null && eventexpr != null) {
415            eventValue = (String) getTextContentIfNodeResult(eval.eval(ctx, eventexpr));
416            if ((eventValue == null)) {
417                throw new SCXMLExpressionException("<send>: event expression \"" + eventexpr
418                        + "\" evaluated to null");
419            }
420        }
421        Map<String, SCXMLIOProcessor> ioProcessors = (Map<String, SCXMLIOProcessor>) ctx.get(SCXMLSystemContext.IOPROCESSORS_KEY);
422        ctx.setLocal(getNamespacesKey(), null);
423        if (exctx.getAppLog().isDebugEnabled()) {
424            exctx.getAppLog().debug("<send>: Dispatching event '" + eventValue
425                    + "' to target '" + targetValue + "' of target type '"
426                    + typeValue + "' with suggested delay of " + wait
427                    + "ms");
428        }
429        exctx.getEventDispatcher().send(ioProcessors, id, targetValue, typeValue, eventValue,
430                payload, hintsValue, wait);
431    }
432
433    /**
434     * Parse delay.
435     *
436     * @param delayString The String value of the delay, in CSS2 format
437     * @param appLog      The application log
438     * @return The parsed delay in milliseconds
439     * @throws SCXMLExpressionException If the delay cannot be parsed
440     */
441    private long parseDelay(final String delayString, final Log appLog)
442            throws SCXMLExpressionException {
443
444        long wait = 0L;
445        long multiplier = 1L;
446
447        if (delayString != null && delayString.trim().length() > 0) {
448
449            String trimDelay = delayString.trim();
450            String numericDelay = trimDelay;
451            if (trimDelay.endsWith(MILLIS)) {
452                numericDelay = trimDelay.substring(0, trimDelay.length() - 2);
453            } else if (trimDelay.endsWith(SECONDS)) {
454                multiplier = MILLIS_IN_A_SECOND;
455                numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
456            } else if (trimDelay.endsWith(MINUTES)) { // Not CSS2
457                multiplier = MILLIS_IN_A_MINUTE;
458                numericDelay = trimDelay.substring(0, trimDelay.length() - 1);
459            }
460
461            try {
462                wait = Long.parseLong(numericDelay);
463            } catch (NumberFormatException nfe) {
464                appLog.error(nfe.getMessage(), nfe);
465                throw new SCXMLExpressionException(nfe.getMessage(), nfe);
466            }
467            wait *= multiplier;
468
469        }
470        return wait;
471    }
472}
473