• 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

49.62
today-context/src/main/java/infra/scripting/groovy/GroovyScriptFactory.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.groovy;
19

20
import org.codehaus.groovy.control.CompilationFailedException;
21
import org.codehaus.groovy.control.CompilerConfiguration;
22
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
23

24
import java.io.IOException;
25
import java.lang.reflect.InaccessibleObjectException;
26
import java.lang.reflect.InvocationTargetException;
27

28
import groovy.lang.GroovyClassLoader;
29
import groovy.lang.GroovyObject;
30
import groovy.lang.MetaClass;
31
import groovy.lang.Script;
32
import infra.beans.factory.BeanClassLoaderAware;
33
import infra.beans.factory.BeanFactory;
34
import infra.beans.factory.BeanFactoryAware;
35
import infra.beans.factory.config.ConfigurableBeanFactory;
36
import infra.core.ConstructorNotFoundException;
37
import infra.lang.Assert;
38
import infra.lang.Nullable;
39
import infra.scripting.ScriptCompilationException;
40
import infra.scripting.ScriptFactory;
41
import infra.scripting.ScriptSource;
42
import infra.scripting.support.ScriptFactoryPostProcessor;
43
import infra.util.ClassUtils;
44
import infra.util.ObjectUtils;
45
import infra.util.ReflectionUtils;
46

47
/**
48
 * {@link ScriptFactory} implementation
49
 * for a Groovy script.
50
 *
51
 * <p>Typically used in combination with a
52
 * {@link ScriptFactoryPostProcessor};
53
 * see the latter's javadoc for a configuration example.
54
 *
55
 * <p>supports Groovy 1.8 and higher.
56
 *
57
 * @author Juergen Hoeller
58
 * @author Rob Harrop
59
 * @author Rod Johnson
60
 * @author <a href="https://github.com/TAKETODAY">海子 Yang</a>
61
 * @see groovy.lang.GroovyClassLoader
62
 * @see ScriptFactoryPostProcessor
63
 * @since 4.0
64
 */
65
public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, BeanClassLoaderAware {
66

67
  private final String scriptSourceLocator;
68

69
  @Nullable
70
  private GroovyObjectCustomizer groovyObjectCustomizer;
71

72
  @Nullable
73
  private CompilerConfiguration compilerConfiguration;
74

75
  @Nullable
76
  private GroovyClassLoader groovyClassLoader;
77

78
  @Nullable
79
  private Class<?> scriptClass;
80

81
  @Nullable
82
  private Class<?> scriptResultClass;
83

84
  @Nullable
85
  private CachedResultHolder cachedResult;
86

87
  private final Object scriptClassMonitor = new Object();
5✔
88

89
  private boolean wasModifiedForTypeCheck = false;
3✔
90

91
  /**
92
   * Create a new GroovyScriptFactory for the given script source.
93
   * <p>We don't need to specify script interfaces here, since
94
   * a Groovy script defines its Java interfaces itself.
95
   *
96
   * @param scriptSourceLocator a locator that points to the source of the script.
97
   * Interpreted by the post-processor that actually creates the script.
98
   */
99
  public GroovyScriptFactory(String scriptSourceLocator) {
2✔
100
    Assert.hasText(scriptSourceLocator, "'scriptSourceLocator' must not be empty");
3✔
101
    this.scriptSourceLocator = scriptSourceLocator;
3✔
102
  }
1✔
103

104
  /**
105
   * Create a new GroovyScriptFactory for the given script source,
106
   * specifying a strategy interface that can create a custom MetaClass
107
   * to supply missing methods and otherwise change the behavior of the object.
108
   *
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 groovyObjectCustomizer a customizer that can set a custom metaclass
112
   * or make other changes to the GroovyObject created by this factory
113
   * (may be {@code null})
114
   * @see GroovyObjectCustomizer#customize
115
   */
116
  public GroovyScriptFactory(String scriptSourceLocator, @Nullable GroovyObjectCustomizer groovyObjectCustomizer) {
117
    this(scriptSourceLocator);
×
118
    this.groovyObjectCustomizer = groovyObjectCustomizer;
×
119
  }
×
120

121
  /**
122
   * Create a new GroovyScriptFactory for the given script source,
123
   * specifying a strategy interface that can create a custom MetaClass
124
   * to supply missing methods and otherwise change the behavior of the object.
125
   *
126
   * @param scriptSourceLocator a locator that points to the source of the script.
127
   * Interpreted by the post-processor that actually creates the script.
128
   * @param compilerConfiguration a custom compiler configuration to be applied
129
   * to the GroovyClassLoader (may be {@code null})
130
   * @see GroovyClassLoader#GroovyClassLoader(ClassLoader, CompilerConfiguration)
131
   */
132
  public GroovyScriptFactory(String scriptSourceLocator, @Nullable CompilerConfiguration compilerConfiguration) {
133
    this(scriptSourceLocator);
×
134
    this.compilerConfiguration = compilerConfiguration;
×
135
  }
×
136

137
  /**
138
   * Create a new GroovyScriptFactory for the given script source,
139
   * specifying a strategy interface that can customize Groovy's compilation
140
   * process within the underlying GroovyClassLoader.
141
   *
142
   * @param scriptSourceLocator a locator that points to the source of the script.
143
   * Interpreted by the post-processor that actually creates the script.
144
   * @param compilationCustomizers one or more customizers to be applied to the
145
   * GroovyClassLoader compiler configuration
146
   * @see CompilerConfiguration#addCompilationCustomizers
147
   * @see org.codehaus.groovy.control.customizers.ImportCustomizer
148
   */
149
  public GroovyScriptFactory(String scriptSourceLocator, CompilationCustomizer... compilationCustomizers) {
150
    this(scriptSourceLocator);
×
151
    if (ObjectUtils.isNotEmpty(compilationCustomizers)) {
×
152
      this.compilerConfiguration = new CompilerConfiguration();
×
153
      this.compilerConfiguration.addCompilationCustomizers(compilationCustomizers);
×
154
    }
155
  }
×
156

157
  @Override
158
  public void setBeanFactory(BeanFactory beanFactory) {
159
    if (beanFactory instanceof ConfigurableBeanFactory) {
3!
160
      ((ConfigurableBeanFactory) beanFactory).ignoreDependencyType(MetaClass.class);
4✔
161
    }
162
  }
1✔
163

164
  @Override
165
  public void setBeanClassLoader(ClassLoader classLoader) {
166
    if (classLoader instanceof GroovyClassLoader &&
3!
167
            (this.compilerConfiguration == null ||
168
                    ((GroovyClassLoader) classLoader).hasCompatibleConfiguration(this.compilerConfiguration))) {
×
169
      this.groovyClassLoader = (GroovyClassLoader) classLoader;
×
170
    }
171
    else {
172
      this.groovyClassLoader = buildGroovyClassLoader(classLoader);
5✔
173
    }
174
  }
1✔
175

176
  /**
177
   * Return the GroovyClassLoader used by this script factory.
178
   */
179
  public GroovyClassLoader getGroovyClassLoader() {
180
    synchronized(this.scriptClassMonitor) {
5✔
181
      if (this.groovyClassLoader == null) {
3!
182
        this.groovyClassLoader = buildGroovyClassLoader(ClassUtils.getDefaultClassLoader());
×
183
      }
184
      return this.groovyClassLoader;
5✔
185
    }
186
  }
187

188
  /**
189
   * Build a {@link GroovyClassLoader} for the given {@code ClassLoader}.
190
   *
191
   * @param classLoader the ClassLoader to build a GroovyClassLoader for
192
   */
193
  protected GroovyClassLoader buildGroovyClassLoader(@Nullable ClassLoader classLoader) {
194
    return compilerConfiguration != null ?
4!
195
            new GroovyClassLoader(classLoader, this.compilerConfiguration) : new GroovyClassLoader(classLoader);
4✔
196
  }
197

198
  @Override
199
  public String getScriptSourceLocator() {
200
    return this.scriptSourceLocator;
3✔
201
  }
202

203
  /**
204
   * Groovy scripts determine their interfaces themselves,
205
   * hence we don't need to explicitly expose interfaces here.
206
   *
207
   * @return {@code null} always
208
   */
209
  @Override
210
  @Nullable
211
  public Class<?>[] getScriptInterfaces() {
212
    return null;
2✔
213
  }
214

215
  /**
216
   * Groovy scripts do not need a config interface,
217
   * since they expose their setters as public methods.
218
   */
219
  @Override
220
  public boolean requiresConfigInterface() {
221
    return false;
2✔
222
  }
223

224
  /**
225
   * Loads and parses the Groovy script via the GroovyClassLoader.
226
   *
227
   * @see groovy.lang.GroovyClassLoader
228
   */
229
  @Override
230
  @Nullable
231
  public Object getScriptedObject(ScriptSource scriptSource, @Nullable Class<?>... actualInterfaces)
232
          throws IOException, ScriptCompilationException {
233

234
    synchronized(this.scriptClassMonitor) {
5✔
235
      try {
236
        Class<?> scriptClassToExecute;
237
        this.wasModifiedForTypeCheck = false;
3✔
238

239
        if (this.cachedResult != null) {
3!
240
          Object result = this.cachedResult.object;
×
241
          this.cachedResult = null;
×
242
          return result;
×
243
        }
244

245
        if (this.scriptClass == null || scriptSource.isModified()) {
6!
246
          // New script content...
247
          this.scriptClass = getGroovyClassLoader().parseClass(
6✔
248
                  scriptSource.getScriptAsString(), scriptSource.suggestedClassName());
3✔
249

250
          if (Script.class.isAssignableFrom(this.scriptClass)) {
5!
251
            // A Groovy script, probably creating an instance: let's execute it.
252
            Object result = executeScript(scriptSource, this.scriptClass);
×
253
            this.scriptResultClass = (result != null ? result.getClass() : null);
×
254
            return result;
×
255
          }
256
          else {
257
            this.scriptResultClass = this.scriptClass;
4✔
258
          }
259
        }
260
        scriptClassToExecute = this.scriptClass;
3✔
261

262
        // Process re-execution outside of the synchronized block.
263
        return executeScript(scriptSource, scriptClassToExecute);
7✔
264
      }
265
      catch (CompilationFailedException ex) {
1✔
266
        this.scriptClass = null;
3✔
267
        this.scriptResultClass = null;
3✔
268
        throw new ScriptCompilationException(scriptSource, ex);
6✔
269
      }
270
    }
271
  }
272

273
  @Override
274
  @Nullable
275
  public Class<?> getScriptedObjectType(ScriptSource scriptSource)
276
          throws IOException, ScriptCompilationException {
277

278
    synchronized(this.scriptClassMonitor) {
5✔
279
      try {
280
        if (this.scriptClass == null || scriptSource.isModified()) {
6!
281
          // New script content...
282
          this.wasModifiedForTypeCheck = true;
3✔
283
          this.scriptClass = getGroovyClassLoader().parseClass(
6✔
284
                  scriptSource.getScriptAsString(), scriptSource.suggestedClassName());
3✔
285

286
          if (Script.class.isAssignableFrom(this.scriptClass)) {
5!
287
            // A Groovy script, probably creating an instance: let's execute it.
288
            Object result = executeScript(scriptSource, this.scriptClass);
×
289
            this.scriptResultClass = (result != null ? result.getClass() : null);
×
290
            this.cachedResult = new CachedResultHolder(result);
×
291
          }
×
292
          else {
293
            this.scriptResultClass = this.scriptClass;
4✔
294
          }
295
        }
296
        return this.scriptResultClass;
5✔
297
      }
298
      catch (CompilationFailedException ex) {
×
299
        this.scriptClass = null;
×
300
        this.scriptResultClass = null;
×
301
        this.cachedResult = null;
×
302
        throw new ScriptCompilationException(scriptSource, ex);
×
303
      }
304
    }
305
  }
306

307
  @Override
308
  public boolean requiresScriptedObjectRefresh(ScriptSource scriptSource) {
309
    synchronized(this.scriptClassMonitor) {
5✔
310
      return scriptSource.isModified() || this.wasModifiedForTypeCheck;
12!
311
    }
312
  }
313

314
  /**
315
   * Instantiate the given Groovy script class and run it if necessary.
316
   *
317
   * @param scriptSource the source for the underlying script
318
   * @param scriptClass the Groovy script class
319
   * @return the result object (either an instance of the script class
320
   * or the result of running the script instance)
321
   * @throws ScriptCompilationException in case of instantiation failure
322
   */
323
  @Nullable
324
  protected Object executeScript(ScriptSource scriptSource, Class<?> scriptClass) throws ScriptCompilationException {
325
    try {
326
      GroovyObject goo = (GroovyObject) ReflectionUtils.accessibleConstructor(scriptClass).newInstance();
9✔
327

328
      if (groovyObjectCustomizer != null) {
3!
329
        // Allow metaclass and other customization.
330
        groovyObjectCustomizer.customize(goo);
×
331
      }
332

333
      if (goo instanceof Script) {
3!
334
        // A Groovy script, probably creating an instance: let's execute it.
335
        return ((Script) goo).run();
×
336
      }
337
      else {
338
        // An instance of the scripted class: let's return it as-is.
339
        return goo;
2✔
340
      }
341
    }
342
    catch (ConstructorNotFoundException ex) {
×
343
      throw new ScriptCompilationException(
×
344
              "No default constructor on Groovy script class: " + scriptClass.getName(), ex);
×
345
    }
346
    catch (InstantiationException ex) {
×
347
      throw new ScriptCompilationException(
×
348
              scriptSource, "Unable to instantiate Groovy script class: " + scriptClass.getName(), ex);
×
349
    }
350
    catch (IllegalAccessException | InaccessibleObjectException ex) {
×
351
      throw new ScriptCompilationException(
×
352
              scriptSource, "Could not access Groovy script constructor: " + scriptClass.getName(), ex);
×
353
    }
354
    catch (InvocationTargetException ex) {
×
355
      throw new ScriptCompilationException(
×
356
              "Failed to invoke Groovy script constructor: " + scriptClass.getName(), ex.getTargetException());
×
357
    }
358
  }
359

360
  @Override
361
  public String toString() {
362
    return "GroovyScriptFactory: script source locator [" + this.scriptSourceLocator + "]";
×
363
  }
364

365
  /**
366
   * Wrapper that holds a temporarily cached result object.
367
   */
368
  private record CachedResultHolder(@Nullable Object object) {
×
369

370
  }
371

372
}
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