View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jexl3.scripting;
19  
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import javax.script.ScriptEngine;
25  import javax.script.ScriptEngineFactory;
26  
27  import org.apache.commons.jexl3.JexlBuilder;
28  import org.apache.commons.jexl3.JexlEngine;
29  import org.apache.commons.jexl3.introspection.JexlPermissions;
30  import org.apache.commons.jexl3.parser.StringParser;
31  
32  /**
33   * Implements the JEXL ScriptEngineFactory for JSF-223.
34   * <p>
35   * Supports the following:<br>
36   * </p>
37   * <ul>
38   * <li>Language short names: "JEXL", "Jexl", "jexl", "JEXL2", "Jexl2", "jexl2", "JEXL3", "Jexl3", "jexl3"</li>
39   * <li>File Extensions: ".jexl", ".jexl2", ".jexl3"</li>
40   * <li>"jexl3" etc. were added for engineVersion="3.0"</li>
41   * </ul>
42   * <p>
43   * See
44   * <a href="https://java.sun.com/javase/6/docs/api/javax/script/package-summary.html">Java Scripting API</a>
45   * Javadoc.
46   *
47   * @since 2.0
48   */
49  public class JexlScriptEngineFactory implements ScriptEngineFactory {
50      /**
51       * The engine and language version.
52       */
53      private static final String JEXL_VERSION = "3.7";
54      /**
55       * The default factory permissions.
56       */
57      private static JexlPermissions defaultPermissions;
58  
59      /**
60       * The engine permissions.
61       */
62      private final JexlPermissions permissions;
63  
64      /**
65       * Default constructor.
66       */
67      public JexlScriptEngineFactory() { this(null); } // Keep Javadoc happy
68  
69      /**
70       * Constructor with permissions.
71       * <p>Meant to reduce dependency to JEXL for extraordinary use case of JSR233.</p>
72       * @param jexlPermissions the permissions instance to use or null to use the {@link JexlScriptEngineFactory} default
73       */
74      public JexlScriptEngineFactory(final JexlPermissions jexlPermissions) {
75          permissions = jexlPermissions != null ? jexlPermissions : defaultPermissions;
76      }
77  
78      @Override
79      public ScriptEngine getScriptEngine() {
80          return new JexlScriptEngine(this);
81      }
82  
83      /**
84       * Creates an engine.
85       * @return the JexlEngine instance, create it if necessary
86       */
87      protected JexlEngine getEngine() {
88          return createJexlEngine();
89      }
90  
91      /**
92       * Creates a new JexlEngine instance.
93       * @return a new JexlEngine instance
94       */
95      protected JexlEngine createJexlEngine() {
96          final JexlBuilder builder = new JexlBuilder()
97            .strict(true)
98            .safe(false)
99            .logger(JexlScriptEngine.LOG)
100           .cache(JexlScriptEngine.CACHE_SIZE);
101         JexlPermissions p = permissions;
102         if (p == null) {
103             p = defaultPermissions;
104             if (p == null) {
105                 p = builder.permissions();
106                 if (p == null) {
107                     p = JexlPermissions.RESTRICTED;
108                 }
109             }
110         }
111         final JexlPermissions required = new JexlPermissions.ClassPermissions(p, JexlScriptEngine.JexlScriptObject.class);
112         builder.permissions(required);
113         return builder.create();
114     }
115 
116     /**
117      * Sets the permissions instance used to create the script engine.
118      * <p>To restore 3.2 <em>unsafe</em> script behavior:</p>
119      * {@code
120      *         JexlScriptEngineFactory.setDefaultPermissions(JexlPermissions.UNRESTRICTED);
121      * }
122      *
123      * @param permissions the permissions instance to use or null to use the {@link JexlBuilder} default
124      * @since 3.6.3
125      */
126     public static void setDefaultPermissions(final JexlPermissions permissions) {
127         defaultPermissions = permissions;
128     }
129 
130     @Override
131     public String getEngineName() {
132         return "JEXL Engine";
133     }
134 
135     @Override
136     public String getEngineVersion() {
137         return JEXL_VERSION;
138     }
139 
140     @Override
141     public List<String> getExtensions() {
142         return Collections.unmodifiableList(Arrays.asList("jexl", "jexl2", "jexl3"));
143     }
144 
145     @Override
146     public String getLanguageName() {
147         return "JEXL";
148     }
149 
150     @Override
151     public String getLanguageVersion() {
152         return JEXL_VERSION;
153     }
154 
155     @Override
156     public String getMethodCallSyntax(final String obj, final String m, final String... args) {
157         final StringBuilder sb = new StringBuilder();
158         sb.append(obj);
159         sb.append('.');
160         sb.append(m);
161         sb.append('(');
162         boolean needComma = false;
163         for(final String arg : args){
164             if (needComma) {
165                 sb.append(',');
166             }
167             sb.append(arg);
168             needComma = true;
169         }
170         sb.append(')');
171         return sb.toString();
172     }
173 
174     @Override
175     public List<String> getMimeTypes() {
176         return Collections.unmodifiableList(Arrays.asList("application/x-jexl",
177                                                           "application/x-jexl2",
178                                                           "application/x-jexl3"));
179     }
180 
181     @Override
182     public List<String> getNames() {
183         return Collections.unmodifiableList(Arrays.asList("JEXL", "Jexl", "jexl",
184                                                           "JEXL2", "Jexl2", "jexl2",
185                                                           "JEXL3", "Jexl3", "jexl3"));
186     }
187 
188     @Override
189     public String getOutputStatement(final String toDisplay) {
190         if (toDisplay == null) {
191             return "JEXL.out.print(null)";
192         }
193         return "JEXL.out.print("+StringParser.escapeString(toDisplay, '\'')+")";
194     }
195 
196     @Override
197     public Object getParameter(final String key) {
198         switch (key) {
199             case ScriptEngine.ENGINE:
200                 return getEngineName();
201             case ScriptEngine.ENGINE_VERSION:
202                 return getEngineVersion();
203             case ScriptEngine.NAME:
204                 return getNames();
205             case ScriptEngine.LANGUAGE:
206                 return getLanguageName();
207             case ScriptEngine.LANGUAGE_VERSION:
208                 return getLanguageVersion();
209             case "THREADING":
210                 /*
211                  * To implement multithreading, the scripting engine context (inherited from AbstractScriptEngine)
212                  * would need to be made thread-safe; so would the setContext/getContext methods.
213                  * It is easier to share the underlying Uberspect and JEXL engine instance, especially
214                  * with an expression cache.
215                  */
216             default:
217                 return null;
218         }
219     }
220 
221     @Override
222     public String getProgram(final String... statements) {
223         final StringBuilder sb = new StringBuilder();
224         for(final String statement : statements){
225             sb.append(statement.trim());
226             if (!statement.endsWith(";")){
227                 sb.append(';');
228             }
229         }
230         return sb.toString();
231     }
232 
233 }