View Javadoc
1   /*
2    * scriptable-dataset (https://github.com/hazendaz/scriptable-dataset)
3    *
4    * Copyright 2011-2025 Hazendaz.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of The Apache Software License,
8    * Version 2.0 which accompanies this distribution, and is available at
9    * https://www.apache.org/licenses/LICENSE-2.0.txt
10   *
11   * Contributors:
12   *     Gunnar Morling
13   *     Hazendaz (Jeremy Landis).
14   */
15  package de.gmorling.scriptabledataset;
16  
17  import de.gmorling.scriptabledataset.handlers.ScriptInvocationHandler;
18  import de.gmorling.scriptabledataset.handlers.StandardHandlerConfig;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  
27  import javax.script.ScriptEngine;
28  import javax.script.ScriptEngineManager;
29  import javax.script.ScriptException;
30  
31  import org.dbunit.dataset.DataSetException;
32  import org.dbunit.dataset.ITable;
33  import org.dbunit.dataset.ITableMetaData;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * ITable implementation, that allows the usage of script statements as field values.
39   */
40  public class ScriptableTable implements ITable {
41  
42      /** The logger. */
43      private final Logger logger = LoggerFactory.getLogger(ScriptableTable.class);
44  
45      /** The wrapped. */
46      private ITable wrapped;
47  
48      /** The engines by prefix. */
49      private Map<String, ScriptEngine> enginesByPrefix = new HashMap<>();
50  
51      /** The handlers by prefix. */
52      private Map<String, List<ScriptInvocationHandler>> handlersByPrefix = new HashMap<>();
53  
54      /**
55       * Creates a new ScriptableTable.
56       *
57       * @param wrapped
58       *            The ITable to be wrapped by this scriptable table. May not be null.
59       * @param configurations
60       *            An list with configurations
61       */
62      public ScriptableTable(ITable wrapped, List<ScriptableDataSetConfig> configurations) {
63  
64          this.wrapped = wrapped;
65  
66          ScriptEngineManager manager = new ScriptEngineManager();
67  
68          // load the engines
69          for (ScriptableDataSetConfig oneConfig : configurations) {
70  
71              ScriptEngine engine = manager.getEngineByName(oneConfig.getLanguageName());
72  
73              if (engine == null) {
74                  throw new RuntimeException(
75                          "No scripting engine found for language \"" + oneConfig.getLanguageName() + "\".");
76              }
77              enginesByPrefix.put(oneConfig.getPrefix(), engine);
78  
79              List<ScriptInvocationHandler> handlers = getAllHandlers(oneConfig);
80  
81              for (ScriptInvocationHandler oneHandler : handlers) {
82                  oneHandler.setScriptEngine(engine);
83              }
84  
85              handlersByPrefix.put(oneConfig.getPrefix(), handlers);
86  
87              logger.info("Registered scripting engine {} for language {}.", engine, oneConfig.getLanguageName());
88          }
89      }
90  
91      @Override
92      public int getRowCount() {
93          return wrapped.getRowCount();
94      }
95  
96      @Override
97      public ITableMetaData getTableMetaData() {
98          return wrapped.getTableMetaData();
99      }
100 
101     @Override
102     public Object getValue(int row, String column) throws DataSetException {
103 
104         Object theValue = wrapped.getValue(row, column);
105 
106         // only strings can be processed
107         if (theValue instanceof String) {
108             String script = (String) theValue;
109 
110             for (Entry<String, ScriptEngine> oneEntry : enginesByPrefix.entrySet()) {
111 
112                 String prefix = oneEntry.getKey();
113 
114                 // found engine for prefix
115                 if (script.startsWith(prefix)) {
116 
117                     ScriptEngine engine = oneEntry.getValue();
118                     script = script.substring(prefix.length());
119 
120                     List<ScriptInvocationHandler> handlers = handlersByPrefix.get(oneEntry.getKey());
121 
122                     // preInvoke
123                     for (ScriptInvocationHandler handler : handlers) {
124                         script = handler.preInvoke(script);
125                     }
126 
127                     logger.debug("Executing script: {}", script);
128 
129                     // the actual script evaluation
130                     try {
131                         theValue = engine.eval(script);
132                     } catch (ScriptException e) {
133                         throw new RuntimeException(e);
134                     }
135 
136                     // call postInvoke in reversed order
137                     Collections.reverse(handlers);
138                     for (ScriptInvocationHandler handler : handlers) {
139                         theValue = handler.postInvoke(theValue);
140                     }
141                 }
142             }
143         }
144 
145         return theValue;
146     }
147 
148     /**
149      * Returns a list with all standard handlers registered for the language of the config and all handlers declared in
150      * the config itself.
151      *
152      * @param config
153      *            A config object.
154      *
155      * @return A list with handlers. Never null.
156      */
157     private List<ScriptInvocationHandler> getAllHandlers(ScriptableDataSetConfig config) {
158 
159         List<ScriptInvocationHandler> theValue = new ArrayList<>(
160                 StandardHandlerConfig.getStandardHandlersByLanguage(config.getLanguageName()));
161 
162         // custom handlers
163         theValue.addAll(config.getHandlers());
164 
165         return theValue;
166     }
167 }