View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
25   */
26  
27  package com.rapiddweller.benerator.engine.statement;
28  
29  import com.rapiddweller.benerator.StorageSystem;
30  import com.rapiddweller.benerator.engine.BeneratorContext;
31  import com.rapiddweller.benerator.engine.Statement;
32  import com.rapiddweller.common.Assert;
33  import com.rapiddweller.common.BeanUtil;
34  import com.rapiddweller.common.ConfigurationError;
35  import com.rapiddweller.common.Context;
36  import com.rapiddweller.common.ConversionException;
37  import com.rapiddweller.common.ErrorHandler;
38  import com.rapiddweller.common.IOUtil;
39  import com.rapiddweller.common.Level;
40  import com.rapiddweller.common.LogCategoriesConstants;
41  import com.rapiddweller.common.ReaderLineIterator;
42  import com.rapiddweller.common.ShellUtil;
43  import com.rapiddweller.common.StringUtil;
44  import com.rapiddweller.common.SystemInfo;
45  import com.rapiddweller.common.converter.LiteralParser;
46  import com.rapiddweller.format.script.Script;
47  import com.rapiddweller.format.script.ScriptUtil;
48  import com.rapiddweller.jdbacl.DBExecutionResult;
49  import com.rapiddweller.jdbacl.DBUtil;
50  import com.rapiddweller.platform.db.DBSystem;
51  import com.rapiddweller.script.Expression;
52  import com.rapiddweller.script.expression.ExpressionUtil;
53  import com.rapiddweller.task.TaskException;
54  import org.apache.logging.log4j.LogManager;
55  import org.apache.logging.log4j.Logger;
56  
57  import java.io.IOException;
58  import java.io.StringReader;
59  import java.io.StringWriter;
60  import java.sql.Connection;
61  import java.sql.SQLException;
62  import java.util.Map;
63  import java.util.Map.Entry;
64  
65  /**
66   * Executes an &lt;evaluate/&gt; from an XML descriptor.<br/>
67   * <br/>
68   * Created at 23.07.2009 17:59:36
69   *
70   * @author Volker Bergmann
71   * @since 0.6.0
72   */
73  public class EvaluateStatement implements Statement {
74  
75    private static final Logger logger = LogManager.getLogger(EvaluateStatement.class);
76  
77    private static final String SHELL = "shell";
78  
79    private static final Map<String, String> extensionMap;
80  
81    static {
82      try {
83        extensionMap = IOUtil.readProperties("com/rapiddweller/benerator/engine/statement/fileTypes.properties");
84      } catch (IOException e) {
85        throw new ConfigurationError(e);
86      }
87    }
88  
89    /**
90     * The Evaluate.
91     */
92    boolean evaluate;
93    /**
94     * The Id ex.
95     */
96    Expression<String> idEx;
97    /**
98     * The Text ex.
99     */
100   Expression<String> textEx;
101   /**
102    * The Uri ex.
103    */
104   Expression<String> uriEx;
105   /**
106    * The Type ex.
107    */
108   Expression<String> typeEx;
109   /**
110    * The Target object ex.
111    */
112   Expression<?> targetObjectEx;
113   /**
114    * The Separator ex.
115    */
116   Expression<Character> separatorEx;
117   /**
118    * The On error ex.
119    */
120   Expression<String> onErrorEx;
121   /**
122    * The Encoding ex.
123    */
124   Expression<String> encodingEx;
125   /**
126    * The Optimize ex.
127    */
128   Expression<Boolean> optimizeEx;
129   /**
130    * The Invalidate ex.
131    */
132   Expression<Boolean> invalidateEx;
133   /**
134    * The Assertion ex.
135    */
136   Expression<?> assertionEx;
137 
138   /**
139    * Instantiates a new Evaluate statement.
140    *
141    * @param evaluate       the evaluate
142    * @param idEx           the id ex
143    * @param textEx         the text ex
144    * @param uriEx          the uri ex
145    * @param typeEx         the type ex
146    * @param targetObjectEx the target object ex
147    * @param separatorEx    the separator ex
148    * @param onErrorEx      the on error ex
149    * @param encodingEx     the encoding ex
150    * @param optimizeEx     the optimize ex
151    * @param invalidateEx   the invalidate ex
152    * @param assertionEx    the assertion ex
153    */
154   public EvaluateStatement(boolean evaluate, Expression<String> idEx, Expression<String> textEx,
155                            Expression<String> uriEx, Expression<String> typeEx, Expression<?> targetObjectEx,
156                            Expression<Character> separatorEx, Expression<String> onErrorEx, Expression<String> encodingEx,
157                            Expression<Boolean> optimizeEx, Expression<Boolean> invalidateEx, Expression<?> assertionEx) {
158     this.evaluate = evaluate;
159     this.idEx = idEx;
160     this.textEx = textEx;
161     this.uriEx = uriEx;
162     this.typeEx = typeEx;
163     this.targetObjectEx = targetObjectEx;
164     this.separatorEx = separatorEx;
165     this.onErrorEx = onErrorEx;
166     this.encodingEx = encodingEx;
167     this.optimizeEx = optimizeEx;
168     this.invalidateEx = invalidateEx;
169     this.assertionEx = assertionEx;
170   }
171 
172   /**
173    * Gets text ex.
174    *
175    * @return the text ex
176    */
177   public Expression<String> getTextEx() {
178     return textEx;
179   }
180 
181   @Override
182   public boolean execute(BeneratorContext context) {
183     try {
184       String onErrorValue = ExpressionUtil.evaluate(onErrorEx, context);
185       if (onErrorValue == null) {
186         onErrorValue = "fatal";
187       }
188 
189       String typeValue = ExpressionUtil.evaluate(typeEx, context);
190       // if type is not defined, derive it from the file extension
191       String uriValue = ExpressionUtil.evaluate(uriEx, context);
192       if (typeEx == null && uriEx != null) {
193         typeValue = mapExtensionOf(uriValue);
194         if ("winshell".equals(typeValue)) {
195           if (!SystemInfo.isWindows()) {
196             throw new ConfigurationError("Need Windows to run file: " + uriValue);
197           } else {
198             typeValue = SHELL;
199           }
200         } else if ("unixshell".equals(typeValue)) {
201           if (SystemInfo.isWindows()) {
202             throw new ConfigurationError("Need Unix system to run file: " + uriValue);
203           } else {
204             typeValue = SHELL;
205           }
206         }
207       }
208       if (uriValue != null) {
209         uriValue = context.resolveRelativeUri(uriValue);
210       }
211       Object targetObject = ExpressionUtil.evaluate(targetObjectEx, context);
212       if (typeValue == null && targetObject instanceof DBSystem) {
213         typeValue = "sql";
214       }
215       if (typeValue == null && targetObject instanceof StorageSystem) {
216         typeValue = "execute";
217       }
218       String encoding = ExpressionUtil.evaluate(encodingEx, context);
219 
220       // run
221       Object result = null;
222       String text = ExpressionUtil.evaluate(textEx, context);
223       if ("sql".equals(typeValue)) {
224         Character separator = ExpressionUtil.evaluate(separatorEx, context);
225         if (separator == null) {
226           separator = ';';
227         }
228         boolean optimize = (optimizeEx != null ? optimizeEx.evaluate(context) : false);
229         Boolean invalidate = (invalidateEx != null ? invalidateEx.evaluate(context) : null);
230         DBExecutionResult executionResult = runSql(uriValue, targetObject, onErrorValue, encoding,
231             text, separator, optimize, invalidate);
232         result = (executionResult != null ? executionResult.result : null);
233       } else if (SHELL.equals(typeValue)) {
234         result = runShell(uriValue, text, onErrorValue);
235       } else if ("execute".equals(typeValue)) {
236         result = ((StorageSystem) targetObject).execute(text);
237       } else {
238         if (typeValue == null) {
239           typeValue = context.getDefaultScript();
240         }
241         if (!StringUtil.isEmpty(uriValue)) {
242           text = IOUtil.getContentOfURI(uriValue);
243         }
244         result = runScript(text, typeValue, onErrorValue, context);
245       }
246       context.setGlobal("result", result);
247       Object assertionValue = ExpressionUtil.evaluate(assertionEx, context);
248       if (assertionValue instanceof String) {
249         assertionValue = LiteralParser.parse((String) assertionValue);
250       }
251       if (assertionValue != null && !(assertionValue instanceof String && ((String) assertionValue).length() == 0)) {
252         if (assertionValue instanceof Boolean) {
253           if (!(Boolean) assertionValue) {
254             getErrorHandler(onErrorValue).handleError("Assertion failed: '" + assertionEx + "'");
255           }
256         } else {
257           if (!BeanUtil.equalsIgnoreType(assertionValue, result)) {
258             getErrorHandler(onErrorValue).handleError("Assertion failed. Expected: '" + assertionValue + "', found: '" + result + "'");
259           }
260         }
261       }
262       String idValue = ExpressionUtil.evaluate(idEx, context);
263       if (idValue != null) {
264         context.setGlobal(idValue, result);
265       }
266       return true;
267     } catch (ConversionException | IOException e) {
268       throw new ConfigurationError(e);
269     }
270   }
271 
272   private static String mapExtensionOf(String uri) {
273     String lcUri = uri.toLowerCase();
274     for (Entry<String, String> entry : extensionMap.entrySet()) {
275       if (lcUri.endsWith(entry.getKey())) {
276         return entry.getValue();
277       }
278     }
279     return null;
280   }
281 
282   private ErrorHandler getErrorHandler(String level) {
283     return new ErrorHandler(getClass().getName(), Level.valueOf(level));
284   }
285 
286   private Object runScript(String text, String type, String onError, Context context) {
287     ErrorHandler errorHandler = new ErrorHandler(getClass().getName(),
288         Level.valueOf(onError));
289     try {
290       Script script = ScriptUtil.parseScriptText(text, type);
291       return script.evaluate(context);
292     } catch (Exception e) {
293       errorHandler.handleError("Error in script evaluation", e);
294       return null;
295     }
296   }
297 
298   private Object runShell(String uri, String text, String onError) {
299     ErrorHandler errorHandler = new ErrorHandler(getClass().getName(), Level.valueOf(onError));
300     StringWriter writer = new StringWriter();
301     if (text != null) {
302       ShellUtil.runShellCommands(new ReaderLineIterator(new StringReader(text)), writer, errorHandler);
303     } else if (uri != null) {
304       ShellUtil.runShellCommand(uri, writer, errorHandler);
305     } else {
306       throw new ConfigurationError("At least uri or text must be provided in <execute> and <evaluate>");
307     }
308     String output = writer.toString();
309     System.out.println(output);
310     return LiteralParser.parse(output);
311   }
312 
313   private DBExecutionResult runSql(String uri, Object targetObject, String onError,
314                                    String encoding, String text, char separator, boolean optimize, Boolean invalidate) {
315     if (targetObject == null) {
316       throw new ConfigurationError("Please specify the 'target' database to execute the SQL script");
317     }
318     Assert.instanceOf(targetObject, DBSystem.class, "target");
319     DBSystem="../../../../../com/rapiddweller/platform/db/DBSystem.html#DBSystem">DBSystem db = (DBSystem) targetObject;
320     if (uri != null) {
321       logger.info("Executing script " + uri);
322     } else if (text != null) {
323       logger.info("Executing inline script");
324     } else {
325       throw new TaskException("No uri or content");
326     }
327     Connection connection = null;
328     DBExecutionResult result = null;
329     ErrorHandler errorHandler = new ErrorHandler(LogCategoriesConstants.SQL, Level.valueOf(onError));
330     try {
331       connection = db.getConnection();
332       if (text != null) {
333         result = DBUtil.executeScript(text, separator, connection, optimize, errorHandler);
334       } else {
335         result = DBUtil.executeScriptFile(uri, encoding, separator, connection, optimize, errorHandler);
336       }
337       if (Boolean.TRUE.equals(invalidate) || (invalidate == null && !evaluate && result.changedStructure)) {
338         db.invalidate(); // possibly we changed the database structure
339       }
340     } catch (Exception sqle) {
341       if (connection != null) {
342         try {
343           connection.rollback();
344         } catch (SQLException e) {
345           // ignore this 2nd exception, we have other problems now (sqle)
346         }
347       }
348       errorHandler.handleError("Error in SQL script execution", sqle);
349     }
350     return result;
351   }
352 
353 }