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