• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 16239315637

12 Jul 2025 03:11PM UTC coverage: 81.778% (+0.005%) from 81.773%
16239315637

push

github

TAKETODAY
:sparkles: Add HttpRequestValues.Processor

59436 of 77625 branches covered (76.57%)

Branch coverage included in aggregate %.

140743 of 167158 relevant lines covered (84.2%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

23.53
today-context/src/main/java/infra/scripting/support/StandardScriptFactory.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.scripting.support;
19

20
import java.io.IOException;
21
import java.lang.reflect.InaccessibleObjectException;
22
import java.lang.reflect.InvocationTargetException;
23

24
import javax.script.Invocable;
25
import javax.script.ScriptEngine;
26
import javax.script.ScriptEngineManager;
27

28
import infra.beans.factory.BeanClassLoaderAware;
29
import infra.core.ConstructorNotFoundException;
30
import infra.lang.Assert;
31
import infra.lang.Nullable;
32
import infra.scripting.ScriptCompilationException;
33
import infra.scripting.ScriptFactory;
34
import infra.scripting.ScriptSource;
35
import infra.util.ClassUtils;
36
import infra.util.ObjectUtils;
37
import infra.util.ReflectionUtils;
38
import infra.util.StringUtils;
39

40
/**
41
 * {@link ScriptFactory} implementation based
42
 * on the JSR-223 script engine abstraction (as included in Java 6+).
43
 * Supports JavaScript, Groovy, JRuby, and other JSR-223 compliant engines.
44
 *
45
 * <p>Typically used in combination with a
46
 * {@link ScriptFactoryPostProcessor};
47
 * see the latter's javadoc for a configuration example.
48
 *
49
 * @author Juergen Hoeller
50
 * @author <a href="https://github.com/TAKETODAY">海子 Yang</a>
51
 * @see ScriptFactoryPostProcessor
52
 * @since 4.0
53
 */
54
public class StandardScriptFactory implements ScriptFactory, BeanClassLoaderAware {
55

56
  @Nullable
57
  private final String scriptEngineName;
58

59
  private final String scriptSourceLocator;
60

61
  @Nullable
62
  private final Class<?>[] scriptInterfaces;
63

64
  @Nullable
1✔
65
  private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
2✔
66

67
  @Nullable
68
  private volatile ScriptEngine scriptEngine;
69

70
  /**
71
   * Create a new StandardScriptFactory for the given script source.
72
   *
73
   * @param scriptSourceLocator a locator that points to the source of the script.
74
   * Interpreted by the post-processor that actually creates the script.
75
   */
76
  public StandardScriptFactory(String scriptSourceLocator) {
77
    this(null, scriptSourceLocator, (Class<?>[]) null);
6✔
78
  }
1✔
79

80
  /**
81
   * Create a new StandardScriptFactory for the given script source.
82
   *
83
   * @param scriptSourceLocator a locator that points to the source of the script.
84
   * Interpreted by the post-processor that actually creates the script.
85
   * @param scriptInterfaces the Java interfaces that the scripted object
86
   * is supposed to implement
87
   */
88
  public StandardScriptFactory(String scriptSourceLocator, Class<?>... scriptInterfaces) {
89
    this(null, scriptSourceLocator, scriptInterfaces);
5✔
90
  }
1✔
91

92
  /**
93
   * Create a new StandardScriptFactory for the given script source.
94
   *
95
   * @param scriptEngineName the name of the JSR-223 ScriptEngine to use
96
   * (explicitly given instead of inferred from the script source)
97
   * @param scriptSourceLocator a locator that points to the source of the script.
98
   * Interpreted by the post-processor that actually creates the script.
99
   */
100
  public StandardScriptFactory(String scriptEngineName, String scriptSourceLocator) {
101
    this(scriptEngineName, scriptSourceLocator, (Class<?>[]) null);
6✔
102
  }
1✔
103

104
  /**
105
   * Create a new StandardScriptFactory for the given script source.
106
   *
107
   * @param scriptEngineName the name of the JSR-223 ScriptEngine to use
108
   * (explicitly given instead of inferred from the script source)
109
   * @param scriptSourceLocator a locator that points to the source of the script.
110
   * Interpreted by the post-processor that actually creates the script.
111
   * @param scriptInterfaces the Java interfaces that the scripted object
112
   * is supposed to implement
113
   */
114
  public StandardScriptFactory(@Nullable String scriptEngineName, String scriptSourceLocator, @Nullable Class<?>... scriptInterfaces) {
2✔
115
    Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty");
3✔
116
    this.scriptEngineName = scriptEngineName;
3✔
117
    this.scriptSourceLocator = scriptSourceLocator;
3✔
118
    this.scriptInterfaces = scriptInterfaces;
3✔
119
  }
1✔
120

121
  @Override
122
  public void setBeanClassLoader(ClassLoader classLoader) {
123
    this.beanClassLoader = classLoader;
×
124
  }
×
125

126
  @Override
127
  public String getScriptSourceLocator() {
128
    return this.scriptSourceLocator;
3✔
129
  }
130

131
  @Override
132
  @Nullable
133
  public Class<?>[] getScriptInterfaces() {
134
    return this.scriptInterfaces;
3✔
135
  }
136

137
  @Override
138
  public boolean requiresConfigInterface() {
139
    return false;
2✔
140
  }
141

142
  /**
143
   * Load and parse the script via JSR-223's ScriptEngine.
144
   */
145
  @Override
146
  @Nullable
147
  public Object getScriptedObject(ScriptSource scriptSource, @Nullable Class<?>... actualInterfaces)
148
          throws IOException, ScriptCompilationException {
149

150
    Object script = evaluateScript(scriptSource);
×
151

152
    if (ObjectUtils.isNotEmpty(actualInterfaces)) {
×
153
      boolean adaptationRequired = false;
×
154
      for (Class<?> requestedIfc : actualInterfaces) {
×
155
        if (script instanceof Class ? !requestedIfc.isAssignableFrom((Class<?>) script) :
×
156
                !requestedIfc.isInstance(script)) {
×
157
          adaptationRequired = true;
×
158
          break;
×
159
        }
160
      }
161
      if (adaptationRequired) {
×
162
        script = adaptToInterfaces(script, scriptSource, actualInterfaces);
×
163
      }
164
    }
165

166
    if (script instanceof Class<?> scriptClass) {
×
167
      try {
168
        return ReflectionUtils.accessibleConstructor(scriptClass).newInstance();
×
169
      }
170
      catch (ConstructorNotFoundException ex) {
×
171
        throw new ScriptCompilationException(
×
172
                "No default constructor on script class: " + scriptClass.getName(), ex);
×
173
      }
174
      catch (InstantiationException ex) {
×
175
        throw new ScriptCompilationException(
×
176
                scriptSource, "Unable to instantiate script class: " + scriptClass.getName(), ex);
×
177
      }
178
      catch (IllegalAccessException | InaccessibleObjectException ex) {
×
179
        throw new ScriptCompilationException(
×
180
                scriptSource, "Could not access script constructor: " + scriptClass.getName(), ex);
×
181
      }
182
      catch (InvocationTargetException ex) {
×
183
        throw new ScriptCompilationException(
×
184
                "Failed to invoke script constructor: " + scriptClass.getName(), ex.getTargetException());
×
185
      }
186
    }
187

188
    return script;
×
189
  }
190

191
  protected Object evaluateScript(ScriptSource scriptSource) {
192
    try {
193
      ScriptEngine scriptEngine = this.scriptEngine;
3✔
194
      if (scriptEngine == null) {
2!
195
        scriptEngine = retrieveScriptEngine(scriptSource);
×
196
        if (scriptEngine == null) {
×
197
          throw new IllegalStateException("Could not determine script engine for " + scriptSource);
×
198
        }
199
        this.scriptEngine = scriptEngine;
×
200
      }
201
      return scriptEngine.eval(scriptSource.getScriptAsString());
×
202
    }
203
    catch (Exception ex) {
1✔
204
      throw new ScriptCompilationException(scriptSource, ex);
6✔
205
    }
206
  }
207

208
  @Nullable
209
  protected ScriptEngine retrieveScriptEngine(ScriptSource scriptSource) {
210
    ScriptEngineManager scriptEngineManager = new ScriptEngineManager(this.beanClassLoader);
6✔
211

212
    if (this.scriptEngineName != null) {
3!
213
      return StandardScriptUtils.retrieveEngineByName(scriptEngineManager, this.scriptEngineName);
×
214
    }
215

216
    if (scriptSource instanceof ResourceScriptSource) {
×
217
      String filename = ((ResourceScriptSource) scriptSource).getResource().getName();
×
218
      if (filename != null) {
×
219
        String extension = StringUtils.getFilenameExtension(filename);
×
220
        if (extension != null) {
×
221
          return scriptEngineManager.getEngineByExtension(extension);
×
222
        }
223
      }
224
    }
225

226
    return null;
×
227
  }
228

229
  @Nullable
230
  protected Object adaptToInterfaces(@Nullable Object script, ScriptSource scriptSource, Class<?>... actualInterfaces) {
231
    Class<?> adaptedIfc;
232
    if (actualInterfaces.length == 1) {
×
233
      adaptedIfc = actualInterfaces[0];
×
234
    }
235
    else {
236
      adaptedIfc = ClassUtils.createCompositeInterface(actualInterfaces, this.beanClassLoader);
×
237
    }
238

239
    if (adaptedIfc != null) {
×
240
      ScriptEngine scriptEngine = this.scriptEngine;
×
241
      if (!(scriptEngine instanceof Invocable invocable)) {
×
242
        throw new ScriptCompilationException(scriptSource,
×
243
                "ScriptEngine must implement Invocable in order to adapt it to an interface: " + scriptEngine);
244
      }
245
      if (script != null) {
×
246
        script = invocable.getInterface(script, adaptedIfc);
×
247
      }
248
      if (script == null) {
×
249
        script = invocable.getInterface(adaptedIfc);
×
250
        if (script == null) {
×
251
          throw new ScriptCompilationException(scriptSource,
×
252
                  "Could not adapt script to interface [%s]".formatted(adaptedIfc.getName()));
×
253
        }
254
      }
255
    }
256

257
    return script;
×
258
  }
259

260
  @Override
261
  @Nullable
262
  public Class<?> getScriptedObjectType(ScriptSource scriptSource)
263
          throws IOException, ScriptCompilationException {
264

265
    return null;
2✔
266
  }
267

268
  @Override
269
  public boolean requiresScriptedObjectRefresh(ScriptSource scriptSource) {
270
    return scriptSource.isModified();
3✔
271
  }
272

273
  @Override
274
  public String toString() {
275
    return "StandardScriptFactory: script source locator [%s]".formatted(this.scriptSourceLocator);
10✔
276
  }
277

278
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc