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.io.IOException;
020
021import javax.xml.parsers.DocumentBuilderFactory;
022import javax.xml.parsers.FactoryConfigurationError;
023import javax.xml.parsers.ParserConfigurationException;
024
025import org.apache.commons.logging.LogFactory;
026import org.apache.commons.scxml2.ActionExecutionContext;
027import org.apache.commons.scxml2.Context;
028import org.apache.commons.scxml2.Evaluator;
029import org.apache.commons.scxml2.PathResolver;
030import org.apache.commons.scxml2.SCXMLExpressionException;
031import org.w3c.dom.*;
032import org.xml.sax.SAXException;
033
034/**
035 * The class in this SCXML object model that corresponds to the
036 * <assign> SCXML element.
037 *
038 */
039public class Assign extends Action implements PathResolverHolder {
040
041    /**
042     * Serial version UID.
043     */
044    private static final long serialVersionUID = 1L;
045
046    /**
047     * Left hand side expression evaluating to a location within
048     * a previously defined XML data tree.
049     */
050    private String location;
051
052    /**
053     * The source where the new XML instance for this location exists.
054     */
055    private String src;
056
057    /**
058     * Expression evaluating to the new value of the variable.
059     */
060    private String expr;
061
062    /**
063     * Defines the nature of the insertion to be performed, default {@link Evaluator.AssignType#REPLACE_CHILDREN}
064     */
065    private Evaluator.AssignType type;
066
067    /**
068     * The attribute name to add at the specified location when using {@link Evaluator.AssignType#ADD_ATTRIBUTE}
069     */
070    private String attr;
071
072    /**
073     * {@link PathResolver} for resolving the "src" result.
074     */
075    private PathResolver pathResolver;
076
077    /**
078     * Constructor.
079     */
080    public Assign() {
081        super();
082    }
083
084    /**
085     * Get the expr that will evaluate to the new value.
086     *
087     * @return Returns the expr.
088     */
089    public String getExpr() {
090        return expr;
091    }
092
093    /**
094     * Set the expr that will evaluate to the new value.
095     *
096     * @param expr The expr to set.
097     */
098    public void setExpr(final String expr) {
099        this.expr = expr;
100    }
101
102    /**
103     * Get the location for a previously defined XML data tree.
104     *
105     * @return Returns the location.
106     */
107    public String getLocation() {
108        return location;
109    }
110
111    /**
112     * Set the location for a previously defined XML data tree.
113     *
114     * @param location The location.
115     */
116    public void setLocation(final String location) {
117        this.location = location;
118    }
119
120    /**
121     * Get the source where the new XML instance for this location exists.
122     *
123     * @return Returns the source.
124     */
125    public String getSrc() {
126        return src;
127    }
128
129    /**
130     * Set the source where the new XML instance for this location exists.
131     *
132     * @param src The source.
133     */
134    public void setSrc(final String src) {
135        this.src = src;
136    }
137
138    /**
139     * Get the {@link PathResolver}.
140     *
141     * @return Returns the pathResolver.
142     */
143    public PathResolver getPathResolver() {
144        return pathResolver;
145    }
146
147    /**
148     * Set the {@link PathResolver}.
149     *
150     * @param pathResolver The pathResolver to set.
151     */
152    public void setPathResolver(final PathResolver pathResolver) {
153        this.pathResolver = pathResolver;
154    }
155
156    public Evaluator.AssignType getType() {
157        return type;
158    }
159
160    public void setType(final Evaluator.AssignType type) {
161        this.type = type;
162    }
163
164    public String getAttr() {
165        return attr;
166    }
167
168    public void setAttr(final String attr) {
169        this.attr = attr;
170    }
171
172    /**
173     * {@inheritDoc}
174     */
175    @Override
176    public void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException {
177        EnterableState parentState = getParentEnterableState();
178        Context ctx = exctx.getContext(parentState);
179        Evaluator evaluator = exctx.getEvaluator();
180        ctx.setLocal(getNamespacesKey(), getNamespaces());
181        Object data;
182        if (src != null && src.trim().length() > 0) {
183            data = getSrcNode();
184        } else {
185            data = evaluator.eval(ctx, expr);
186        }
187
188        evaluator.evalAssign(ctx, location, data, type, attr);
189        if (exctx.getAppLog().isDebugEnabled()) {
190            exctx.getAppLog().debug("<assign>: '" + location + "' updated");
191        }
192        // TODO: introduce a optional 'trace.change' setting or something alike to enable .change events,
193       //        but don't do this by default as it can interfere with transitions not expecting such events
194        /*
195        if ((Evaluator.XPATH_DATA_MODEL.equals(evaluator.getSupportedDatamodel()) && location.startsWith("$") && ctx.has(location.substring(1))
196                || ctx.has(location))) {
197            TriggerEvent ev = new TriggerEvent(location + ".change", TriggerEvent.CHANGE_EVENT);
198            exctx.getInternalIOProcessor().addEvent(ev);
199        }
200        */
201        ctx.setLocal(getNamespacesKey(), null);
202    }
203
204    /**
205     * Get the {@link Node} the "src" attribute points to.
206     *
207     * @return The node the "src" attribute points to.
208     */
209    private Node getSrcNode() {
210        String resolvedSrc = src;
211        if (pathResolver != null) {
212            resolvedSrc = pathResolver.resolvePath(src);
213        }
214        Document doc = null;
215        try {
216            doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(resolvedSrc);
217        } catch (FactoryConfigurationError t) {
218            logError(t);
219        } catch (SAXException e) {
220            logError(e);
221        } catch (IOException e) {
222            logError(e);
223        } catch (ParserConfigurationException e) {
224            logError(e);
225        }
226        if (doc == null) {
227            return null;
228        }
229        return doc.getDocumentElement();
230    }
231
232    /**
233     * @param throwable The throwable to log about
234     */
235    private void logError(Throwable throwable) {
236        org.apache.commons.logging.Log log = LogFactory.
237            getLog(Assign.class);
238        log.error(throwable.getMessage(), throwable);
239    }
240}