001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     vpasquier <vpasquier@nuxeo.com>
018 *     slacoin <slacoin@nuxeo.com>
019 */
020package org.nuxeo.ecm.automation.core.trace;
021
022import static org.nuxeo.ecm.automation.core.Constants.LF;
023
024import java.io.BufferedWriter;
025import java.io.IOException;
026import java.util.Arrays;
027import java.util.Calendar;
028import java.util.HashMap;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.automation.OperationContext;
036import org.nuxeo.ecm.automation.OperationType;
037import org.nuxeo.ecm.automation.core.impl.InvokableMethod;
038import org.nuxeo.ecm.automation.core.scripting.Expression;
039
040/**
041 * @since 5.7.3
042 */
043public class Call {
044
045    private static final Log log = LogFactory.getLog(Call.class);
046
047    /**
048     * Black listing of mvel expressions which should not be evaluated by the Automation traces for debugging purpose
049     *
050     * @since 8.1
051     */
052    public static final String[] MVEL_BLACK_LIST_EXPR = new String[] { "getNextId" };
053
054    protected final String chainId;
055
056    protected final String aliases;
057
058    protected final OperationType type;
059
060    protected final List<Trace> nested = new LinkedList<>();
061
062    protected final Details details;
063
064    protected Call(OperationType chain, OperationType op, Details details) {
065        type = op;
066        chainId = chain.getId();
067        aliases = Arrays.toString(chain.getAliases());
068        this.details = details;
069    }
070
071    public Call(OperationType chain, OperationType op) {
072        this(chain, op, new Details());
073    }
074
075    public Call(OperationType chain, OperationContext context, OperationType type, InvokableMethod method,
076            Map<String, Object> parms) {
077        this(chain, type, new Details(context, method, parms));
078    }
079
080    protected static class Details {
081
082        protected final Map<String, Object> parameters = new HashMap<>();
083
084        protected final Map<String, Object> variables = new HashMap<>();
085
086        protected final InvokableMethod method;
087
088        protected final Object input;
089
090        protected Object output;
091
092        protected Details() {
093            method = null;
094            input = null;
095        }
096
097        protected Details(OperationContext context, InvokableMethod method, Map<String, Object> parms) {
098            this.method = method;
099            input = context.getInput();
100            variables.putAll(context);
101            parms.forEach(new Evaluator(context)::inject);
102        }
103
104        protected class Evaluator {
105
106            protected final OperationContext context;
107
108            protected Evaluator(OperationContext context) {
109                this.context = context;
110            }
111
112            protected void inject(String key, Object value) {
113                if (!(value instanceof Expression)) {
114                    parameters.put(key, value);
115                    return;
116                }
117                Expression exp = (Expression) value;
118                for (String mvelExpr : MVEL_BLACK_LIST_EXPR) {
119                    if (exp.getExpr().contains(mvelExpr)) {
120                        parameters.put(key, new ExpressionParameter(key,
121                                String.format("Cannot be evaluated in traces when using '%s' expression", mvelExpr)));
122                        return;
123                    }
124                }
125                try {
126                    parameters.put(key, new ExpressionParameter(key, exp.eval(context)));
127                    return;
128                } catch (RuntimeException e) {
129                    log.warn("Cannot evaluate mvel expression for parameter: " + key, e);
130                }
131            }
132        }
133
134    }
135
136    /**
137     * @since 7.1
138     */
139    public static class ExpressionParameter {
140
141        protected final String parameterId;
142
143        protected final Object parameterValue;
144
145        public ExpressionParameter(String parameterId, Object parameterValue) {
146            this.parameterId = parameterId;
147            this.parameterValue = parameterValue;
148        }
149
150        public Object getParameterValue() {
151            return parameterValue;
152        }
153
154        public String getParameterId() {
155
156            return parameterId;
157        }
158    }
159
160    public OperationType getType() {
161        return type;
162    }
163
164    public InvokableMethod getMethod() {
165        return details.method;
166    }
167
168    public Map<String, Object> getParameters() {
169        return details.parameters;
170    }
171
172    public Map<String, Object> getVariables() {
173        return details.variables;
174    }
175
176    public Object getInput() {
177        return details.input;
178    }
179
180    public Object getOutput() {
181        return details.output;
182    }
183
184    public List<Trace> getNested() {
185        return nested;
186    }
187
188    public String getChainId() {
189        return chainId;
190    }
191
192    public String getAliases() {
193        return aliases;
194    }
195
196    /**
197     * @since 9.3
198     */
199    public void print(BufferedWriter writer) throws IOException {
200        try {
201            writer.append(LF);
202            writer.append(LF);
203            writer.append("****** ");
204            writer.append(getType().getId());
205            writer.append(" ******");
206            writer.append(LF);
207            writer.append("Chain ID: ");
208            writer.append(getChainId());
209            if (getAliases() != null) {
210                writer.append(LF);
211                writer.append("Chain Aliases: ");
212                writer.append(getAliases());
213            }
214            writer.append(LF);
215            writer.append("Class: ");
216            writer.append(getType().getType().getSimpleName());
217            writer.append(LF);
218            writer.append("Method: '");
219            writer.append(getMethod().getMethod().getName());
220            writer.append("' | Input Type: ");
221            writer.append(getMethod().getConsume().getName());
222            writer.append(" | Output Type: ");
223            writer.append(getMethod().getProduce().getName());
224            writer.append(LF);
225            writer.append("Input: ");
226            writer.append(getInput() == null ? "null" : getInput().toString());
227            if (!getParameters().isEmpty()) {
228                writer.append(LF);
229                writer.append("Parameters ");
230                for (String parameter : getParameters().keySet()) {
231                    writer.append(" | ");
232                    writer.append("Name: ");
233                    writer.append(parameter);
234                    writer.append(", Value: ");
235                    Object value = getParameters().get(parameter);
236                    if (value instanceof Call.ExpressionParameter) {
237                        value = String.format("Expr:(id=%s | value=%s)",
238                                ((Call.ExpressionParameter) getParameters().get(parameter)).getParameterId(),
239                                ((Call.ExpressionParameter) getParameters().get(parameter)).getParameterValue());
240                    }
241                    writer.append(value.toString());
242                }
243            }
244            if (!getVariables().isEmpty()) {
245                writer.append(LF);
246                writer.append("Context Variables");
247                for (String keyVariable : getVariables().keySet()) {
248                    writer.append(" | ");
249                    writer.append("Key: ");
250                    writer.append(keyVariable);
251                    writer.append(", Value: ");
252                    Object variable = getVariables().get(keyVariable);
253                    if (variable instanceof Calendar) {
254                        writer.append(((Calendar) variable).getTime().toString());
255                    } else {
256                        writer.append(variable == null ? "null" : variable.toString());
257                    }
258                }
259            }
260            if (!getNested().isEmpty()) {
261                printHeading("start sub chain", writer);
262                for (Trace trace : getNested()) {
263                    writer.append(LF);
264                    trace.print(writer);
265                    writer.append(LF);
266                }
267                printHeading("end sub chain", writer);
268            }
269        } catch (IOException e) {
270            log.error("Nuxeo TracePrinter cannot write traces output", e);
271        }
272    }
273
274    protected void printHeading(String heading, BufferedWriter writer) throws IOException {
275        writer.append(LF).append(LF).append("****** ").append(heading).append(" ******");
276    }
277}