View Javadoc
1   /*
2    *    Copyright 2010-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.migration.hook;
17  
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.IOException;
21  import java.io.InputStreamReader;
22  import java.io.PrintStream;
23  import java.nio.charset.Charset;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.Properties;
30  import java.util.Set;
31  
32  import javax.script.Bindings;
33  import javax.script.Invocable;
34  import javax.script.ScriptContext;
35  import javax.script.ScriptEngine;
36  import javax.script.ScriptEngineManager;
37  import javax.script.ScriptException;
38  
39  import org.apache.ibatis.migration.MigrationException;
40  import org.apache.ibatis.migration.options.SelectedPaths;
41  import org.apache.ibatis.migration.utils.Util;
42  
43  public class Jsr223HookScript implements HookScript {
44  
45    private static final String MIGRATION_PATHS = "migrationPaths";
46  
47    private static final String KEY_FUNCTION = "_function";
48    private static final String KEY_OBJECT = "_object";
49    private static final String KEY_METHOD = "_method";
50    private static final String KEY_ARG = "_arg";
51  
52    protected final String language;
53    protected final File scriptFile;
54    protected final String charset;
55    protected final Properties variables;
56    protected final SelectedPaths paths;
57    protected final PrintStream printStream;
58  
59    protected String functionName;
60    protected String objectName;
61    protected String methodName;
62    protected List<String> args = new ArrayList<>();
63    protected Map<String, String> localVars = new HashMap<>();
64  
65    public Jsr223HookScript(String language, File scriptFile, String charset, String[] options, SelectedPaths paths,
66        Properties variables, PrintStream printStream) {
67      this.language = language;
68      this.scriptFile = scriptFile;
69      this.charset = charset;
70      this.paths = paths;
71      this.variables = variables;
72      this.printStream = printStream;
73      for (String option : options) {
74        int sep = option.indexOf('=');
75        if (sep > -1) {
76          String key = option.substring(0, sep);
77          String value = option.substring(sep + 1);
78          if (KEY_FUNCTION.equals(key)) {
79            functionName = value;
80          } else if (KEY_METHOD.equals(key)) {
81            methodName = value;
82          } else if (KEY_OBJECT.equals(key)) {
83            objectName = value;
84          } else if (KEY_ARG.equals(key)) {
85            args.add(value);
86          } else {
87            localVars.put(key, value);
88          }
89        }
90      }
91    }
92  
93    @Override
94    public void execute(Map<String, Object> bindingMap) {
95      ScriptEngineManager manager = new ScriptEngineManager();
96      ScriptEngine engine = manager.getEngineByName(language);
97      // bind global/local variables defined in the environment file
98      Bindings bindings = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
99      bindVariables(bindingMap, variables.entrySet());
100     bindVariables(bindingMap, localVars.entrySet());
101     bindings.put(MIGRATION_PATHS, paths);
102     bindings.putAll(bindingMap);
103     try {
104       printStream.println(Util.horizontalLine("Applying JSR-223 hook : " + scriptFile.getName(), 80));
105       try (
106           InputStreamReader stream = new InputStreamReader(new FileInputStream(scriptFile), Charset.forName(charset))) {
107         engine.eval(stream);
108       }
109       if (functionName != null || objectName != null && methodName != null) {
110         Invocable invocable = (Invocable) engine;
111         if (functionName != null) {
112           printStream.println(Util.horizontalLine("Invoking function : " + functionName, 80));
113           invocable.invokeFunction(functionName, args.toArray());
114         } else {
115           printStream.println(Util.horizontalLine("Invoking method : " + methodName, 80));
116           Object targetObject = engine.get(objectName);
117           invocable.invokeMethod(targetObject, methodName, args.toArray());
118         }
119       }
120       // store vars in bindings to the per-operation map
121       bindVariables(bindingMap, bindings.entrySet());
122     } catch (ClassCastException e) {
123       throw new MigrationException(
124           "Script engine '" + engine.getClass().getName() + "' does not support function/method invocation.", e);
125     } catch (IOException e) {
126       throw new MigrationException("Failed to read JSR-223 hook script file.", e);
127     } catch (ScriptException e) {
128       throw new MigrationException("Failed to execute JSR-223 hook script.", e);
129     } catch (NoSuchMethodException e) {
130       throw new MigrationException("Method or function not found in JSR-223 hook script: " + functionName, e);
131     }
132   }
133 
134   private <S, T> void bindVariables(Map<String, Object> bindingMap, Set<Entry<S, T>> vars) {
135     for (Entry<S, T> entry : vars) {
136       bindingMap.put((String) entry.getKey(), entry.getValue());
137     }
138   }
139 }