001: /*
002: * %W% %E% %U%
003: *
004: * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
005: * SUN PROPRIETARY/CONFIDENTAIL. Use is subject to license terms.
006: */
007:
008: package javax.script;
009:
010: import java.util.*;
011: import java.net.URL;
012: import java.io.*;
013: import java.security.*;
014: import sun.misc.Launcher;
015: import sun.misc.Resource;
016: import sun.reflect.Reflection;
017: import sun.security.util.SecurityConstants;
018:
019: /**
020: * The <code>ScriptEngineManager</code> implements a discovery and instantiation
021: * mechanism for <code>ScriptEngine</code> classes and also maintains a
022: * collection of key/value pairs storing state shared by all engines created
023: * by the Manager.
024: * <br><br>
025: * The Discovery feature uses the Service Provider mechanism described in the <i>Jar
026: * File Specification</i> to enumerate all implementations of
027: * <code>ScriptEngineFactory</code> which can be loaded by the thread context
028: * ClassLoader. If the current security policy does not allow access to thread context
029: * ClassLoader, then bootstrap loader is used. The <code>ScriptEngineManager</code> provides
030: * a method to return an array of all these factories as well as utility methods which
031: * look up factories on the basis of language name, file extension and
032: * mime type.
033: * <p>
034: * The <code>Bindings</code> of key/value pairs, referred to as the "Global Scope" maintained
035: * by the manager is available to all instances of <code>ScriptEngine</code> created
036: * by the <code>ScriptEngineManager</code>. The values in the <code>Bindings</code> are
037: * generally exposed in all scripts.
038: *
039: * @author Mike Grogan
040: * @author A. Sundararajan
041: * @since 1.6
042: */
043: public class ScriptEngineManager {
044: private static final boolean DEBUG = false;
045:
046: /**
047: * The constructor checks for implementors of
048: * <code>ScriptEngineFactory</code> using the mechanism for
049: * discovering service providers described in the Jar File Specification.<br><br>
050: * Namely, it looks for resources named
051: * <i>META-INF/services/javax.script.ScriptEngineFactory</i> in the thread context <code>ClassLoader</code>. Each line in such
052: * a resource names a class implementing <code>ScriptEngineFactory</code>.
053: * An instance of each of these classes is created and stored in the <code>engineSpis</code>
054: * <code>HashSet</code> field. Invalid or incorrect entries are ignored. If thread context
055: * loader is not accessible by current security policy, then bootstrap loader will be used.
056: */
057: public ScriptEngineManager() {
058: ClassLoader ctxtLoader = Thread.currentThread()
059: .getContextClassLoader();
060: if (canCallerAccessLoader(ctxtLoader)) {
061: if (DEBUG)
062: System.out.println("using " + ctxtLoader);
063: init(ctxtLoader);
064: } else {
065: if (DEBUG)
066: System.out.println("using bootstrap loader");
067: init(null);
068: }
069: }
070:
071: /**
072: * The constructor checks for implementors of
073: * <code>ScriptEngineFactory</code> using the mechanism for
074: * discovering service providers described in the Jar File Specification.<br><br>
075: * Namely, it looks for resources named
076: * <i>META-INF/services/javax.script.ScriptEngineFactory</i> in the given <code>ClassLoader</code>. Each line in such
077: * a resource names a class implementing <code>ScriptEngineFactory</code>.
078: * An instance of each of these classes is created and stored in the <code>engineSpis</code>
079: * <code>HashSet</code> field. Invalid or incorrect entries are ignored.
080: *
081: * @param loader ClassLoader used to discover factory resources.
082: */
083: public ScriptEngineManager(ClassLoader loader) {
084: init(loader);
085: }
086:
087: private void init(final ClassLoader loader) {
088: globalScope = new SimpleBindings();
089: engineSpis = new HashSet<ScriptEngineFactory>();
090: nameAssociations = new HashMap<String, ScriptEngineFactory>();
091: extensionAssociations = new HashMap<String, ScriptEngineFactory>();
092: mimeTypeAssociations = new HashMap<String, ScriptEngineFactory>();
093:
094: try {
095: final String resourceName = "META-INF/services/javax.script.ScriptEngineFactory";
096: final List<URL> urls = new ArrayList<URL>();
097: try {
098: AccessController
099: .doPrivileged(new PrivilegedExceptionAction() {
100: public Object run() throws IOException {
101: Enumeration<URL> enumResources = getResources(
102: loader, resourceName);
103: while (enumResources.hasMoreElements()) {
104: urls.add(enumResources
105: .nextElement());
106: }
107: return null;
108: }
109: });
110: } catch (PrivilegedActionException pae) {
111: if (DEBUG)
112: pae.printStackTrace();
113: }
114:
115: for (final URL url : urls) {
116: try {
117: InputStream stream = (InputStream) AccessController
118: .doPrivileged(new PrivilegedExceptionAction() {
119: public Object run() throws IOException {
120: return url.openStream();
121: }
122: });
123: BufferedReader reader = new BufferedReader(
124: new InputStreamReader(stream));
125: String line = null;
126: while (null != (line = reader.readLine())) {
127: addSpi(line, loader);
128: }
129: } catch (Exception e) {
130: // a single resource URL open/read failed, may be others would succed
131: if (DEBUG)
132: e.printStackTrace();
133: // continue the loop...
134: }
135: }
136: } catch (IOException e) {
137: // getResources call failed.
138: if (DEBUG)
139: e.printStackTrace();
140: }
141: }
142:
143: /**
144: * <code>setBindings</code> stores the specified <code>Bindings</code>
145: * in the <code>globalScope</code> field.
146: * @param bindings The specified <code>Bindings</code>
147: */
148: public void setBindings(Bindings bindings) {
149: if (bindings == null) {
150: throw new IllegalArgumentException(
151: "Global scope cannot be null.");
152: }
153:
154: globalScope = bindings;
155: }
156:
157: /**
158: * <code>getBindings</code> returns the value of the <code>globalScope</code> field.
159: * @return The globalScope field.
160: */
161: public Bindings getBindings() {
162: return globalScope;
163: }
164:
165: /**
166: * Sets the specified key/value pair in the Global Scope.
167: * @param key Key to set
168: * @param value Value to set.
169: * @throws NullPointerException if key is null
170: */
171: public void put(String key, Object value) {
172: globalScope.put(key, value);
173: }
174:
175: /**
176: * Gets the value for the specified key in the Global Scope
177: * @param key The key whose value is to be returned.
178: * @return The value for the specified key.
179: */
180: public Object get(String key) {
181: return globalScope.get(key);
182: }
183:
184: /**
185: * Looks up and creates a <code>ScriptEngine</code> for a given name.
186: * The algorithm first searches for a <code>ScriptEngineFactory</code> that has been
187: * registered as a handler for the specified name using the <code>registerEngineName</code>
188: * method.
189: * <br><br> If one is not found, it searches the array of <code>ScriptEngineFactory</code> instances
190: * stored by the constructor for one with the specified name. If a <code>ScriptEngineFactory</code>
191: * is found by either method, it is used to create instance of <code>ScriptEngine</code>.
192: * @param shortName The short name of the <code>ScriptEngine</code> implementation.
193: * returned by the <code>getName</code> method of its <code>ScriptEngineFactory</code>.
194: * @return A <code>ScriptEngine</code> created by the factory located in the search. Returns null
195: * if no such factory was found. The <code>ScriptEngineManager</code> sets its own <code>globalScope</code>
196: * <code>Bindings</code> as the <code>GLOBAL_SCOPE</code> <code>Bindings</code> of the newly
197: * created <code>ScriptEngine</code>.
198: */
199: public ScriptEngine getEngineByName(String shortName) {
200: if (shortName == null)
201: throw new NullPointerException();
202: //look for registered name first
203: Object obj;
204: if (null != (obj = nameAssociations.get(shortName))) {
205: ScriptEngineFactory spi = (ScriptEngineFactory) obj;
206: try {
207: ScriptEngine engine = spi.getScriptEngine();
208: engine.setBindings(getBindings(),
209: ScriptContext.GLOBAL_SCOPE);
210: return engine;
211: } catch (Exception exp) {
212: if (DEBUG)
213: exp.printStackTrace();
214: }
215: }
216:
217: for (ScriptEngineFactory spi : engineSpis) {
218: List<String> names = null;
219: try {
220: names = spi.getNames();
221: } catch (Exception exp) {
222: if (DEBUG)
223: exp.printStackTrace();
224: }
225:
226: if (names != null) {
227: for (String name : names) {
228: if (shortName.equals(name)) {
229: try {
230: ScriptEngine engine = spi.getScriptEngine();
231: engine.setBindings(getBindings(),
232: ScriptContext.GLOBAL_SCOPE);
233: return engine;
234: } catch (Exception exp) {
235: if (DEBUG)
236: exp.printStackTrace();
237: }
238: }
239: }
240: }
241: }
242:
243: return null;
244: }
245:
246: /**
247: * Look up and create a <code>ScriptEngine</code> for a given extension. The algorithm
248: * used by <code>getEngineByName</code> is used except that the search starts
249: * by looking for a <code>ScriptEngineFactory</code> registered to handle the
250: * given extension using <code>registerEngineExtension</code>.
251: * @param extension The given extension
252: * @return The engine to handle scripts with this extension. Returns <code>null</code>
253: * if not found.
254: */
255: public ScriptEngine getEngineByExtension(String extension) {
256: if (extension == null)
257: throw new NullPointerException();
258: //look for registered extension first
259: Object obj;
260: if (null != (obj = extensionAssociations.get(extension))) {
261: ScriptEngineFactory spi = (ScriptEngineFactory) obj;
262: try {
263: ScriptEngine engine = spi.getScriptEngine();
264: engine.setBindings(getBindings(),
265: ScriptContext.GLOBAL_SCOPE);
266: return engine;
267: } catch (Exception exp) {
268: if (DEBUG)
269: exp.printStackTrace();
270: }
271: }
272:
273: for (ScriptEngineFactory spi : engineSpis) {
274: List<String> exts = null;
275: try {
276: exts = spi.getExtensions();
277: } catch (Exception exp) {
278: if (DEBUG)
279: exp.printStackTrace();
280: }
281: if (exts == null)
282: continue;
283: for (String ext : exts) {
284: if (extension.equals(ext)) {
285: try {
286: ScriptEngine engine = spi.getScriptEngine();
287: engine.setBindings(getBindings(),
288: ScriptContext.GLOBAL_SCOPE);
289: return engine;
290: } catch (Exception exp) {
291: if (DEBUG)
292: exp.printStackTrace();
293: }
294: }
295: }
296: }
297: return null;
298: }
299:
300: /**
301: * Look up and create a <code>ScriptEngine</code> for a given mime type. The algorithm
302: * used by <code>getEngineByName</code> is used except that the search starts
303: * by looking for a <code>ScriptEngineFactory</code> registered to handle the
304: * given mime type using <code>registerEngineMimeType</code>.
305: * @param mimeType The given mime type
306: * @return The engine to handle scripts with this mime type. Returns <code>null</code>
307: * if not found.
308: */
309: public ScriptEngine getEngineByMimeType(String mimeType) {
310: if (mimeType == null)
311: throw new NullPointerException();
312: //look for registered types first
313: Object obj;
314: if (null != (obj = mimeTypeAssociations.get(mimeType))) {
315: ScriptEngineFactory spi = (ScriptEngineFactory) obj;
316: try {
317: ScriptEngine engine = spi.getScriptEngine();
318: engine.setBindings(getBindings(),
319: ScriptContext.GLOBAL_SCOPE);
320: return engine;
321: } catch (Exception exp) {
322: if (DEBUG)
323: exp.printStackTrace();
324: }
325: }
326:
327: for (ScriptEngineFactory spi : engineSpis) {
328: List<String> types = null;
329: try {
330: types = spi.getMimeTypes();
331: } catch (Exception exp) {
332: if (DEBUG)
333: exp.printStackTrace();
334: }
335: if (types == null)
336: continue;
337: for (String type : types) {
338: if (mimeType.equals(type)) {
339: try {
340: ScriptEngine engine = spi.getScriptEngine();
341: engine.setBindings(getBindings(),
342: ScriptContext.GLOBAL_SCOPE);
343: return engine;
344: } catch (Exception exp) {
345: if (DEBUG)
346: exp.printStackTrace();
347: }
348: }
349: }
350: }
351: return null;
352: }
353:
354: /**
355: * Returns an array whose elements are instances of all the <code>ScriptEngineFactory</code> classes
356: * found by the discovery mechanism.
357: * @return List of all discovered <code>ScriptEngineFactory</code>s.
358: */
359: public List<ScriptEngineFactory> getEngineFactories() {
360: List<ScriptEngineFactory> res = new ArrayList<ScriptEngineFactory>(
361: engineSpis.size());
362: for (ScriptEngineFactory spi : engineSpis) {
363: res.add(spi);
364: }
365: return Collections.unmodifiableList(res);
366: }
367:
368: /**
369: * Registers a <code>ScriptEngineFactory</code> to handle a language
370: * name. Overrides any such association found using the Discovery mechanism.
371: * @param name The name to be associated with the <code>ScriptEngineFactory</code>.
372: * @param factory The class to associate with the given name.
373: */
374: public void registerEngineName(String name,
375: ScriptEngineFactory factory) {
376: if (name == null || factory == null)
377: throw new NullPointerException();
378: nameAssociations.put(name, factory);
379: }
380:
381: /**
382: * Registers a <code>ScriptEngineFactory</code> to handle a mime type.
383: * Overrides any such association found using the Discovery mechanism.
384: *
385: * @param type The mime type to be associated with the
386: * <code>ScriptEngineFactory</code>.
387: *
388: * @param factory The class to associate with the given mime type.
389: */
390: public void registerEngineMimeType(String type,
391: ScriptEngineFactory factory) {
392: if (type == null || factory == null)
393: throw new NullPointerException();
394: mimeTypeAssociations.put(type, factory);
395: }
396:
397: /**
398: * Registers a <code>ScriptEngineFactory</code> to handle an extension.
399: * Overrides any such association found using the Discovery mechanism.
400: *
401: * @param extension The extension type to be associated with the
402: * <code>ScriptEngineFactory</code>.
403: * @param factory The class to associate with the given extension.
404: */
405: public void registerEngineExtension(String extension,
406: ScriptEngineFactory factory) {
407: if (extension == null || factory == null)
408: throw new NullPointerException();
409: extensionAssociations.put(extension, factory);
410: }
411:
412: /**
413: * Utility method to remove everything following "#" from string, strip
414: * spaces and attempt to instantiate class whose name is what is left over.
415: * Fail silently if class cannot be loaded or does not exist
416: * @param line containing class name
417: * @param loader used to load engine factory classes
418: */
419: private void addSpi(String line, ClassLoader loader) {
420: int hash = line.indexOf('#');
421: if (hash != -1) {
422: line = line.substring(0, hash);
423: }
424:
425: String trimmedLine = line.trim();
426: if (!trimmedLine.equals("")) {
427: String mungedName = null;
428: try {
429: mungedName = trimmedLine.replace('/', '.');
430: Class clasz = Class.forName(mungedName, false, loader);
431: if (ScriptEngineFactory.class.isAssignableFrom(clasz)) {
432: Object obj = clasz.newInstance();
433: engineSpis.add((ScriptEngineFactory) obj);
434: }
435: } catch (Exception e) {
436: if (DEBUG)
437: e.printStackTrace();
438: }
439:
440: }
441: }
442:
443: /** Set of script engine factories discovered. */
444: private HashSet<ScriptEngineFactory> engineSpis;
445:
446: /** Map of engine name to script engine factory. */
447: private HashMap<String, ScriptEngineFactory> nameAssociations;
448:
449: /** Map of script file extension to script engine factory. */
450: private HashMap<String, ScriptEngineFactory> extensionAssociations;
451:
452: /** Map of script script MIME type to script engine factory. */
453: private HashMap<String, ScriptEngineFactory> mimeTypeAssociations;
454:
455: /** Global bindings associated with script engines created by this manager. */
456: private Bindings globalScope;
457:
458: private boolean canCallerAccessLoader(ClassLoader loader) {
459: SecurityManager sm = System.getSecurityManager();
460: if (sm != null) {
461: ClassLoader callerLoader = getCallerClassLoader();
462: if (callerLoader != null) {
463: if (loader != callerLoader
464: || !isAncestor(loader, callerLoader)) {
465: try {
466: sm
467: .checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
468: } catch (SecurityException exp) {
469: if (DEBUG)
470: exp.printStackTrace();
471: return false;
472: }
473: } // else fallthru..
474: } // else fallthru..
475: } // else fallthru..
476:
477: return true;
478: }
479:
480: // Note that this code is same as ClassLoader.getCallerClassLoader().
481: // But, that method is package private and hence we can't call here.
482: private ClassLoader getCallerClassLoader() {
483: Class caller = Reflection.getCallerClass(3);
484: if (caller == null) {
485: return null;
486: }
487: return caller.getClassLoader();
488: }
489:
490: // Returns resources of given name visible from given loader. Handles
491: // bootstrap loader as well.
492: private Enumeration<URL> getResources(final ClassLoader loader,
493: final String name) throws IOException {
494: if (loader != null) {
495: return loader.getResources(name);
496: } else {
497: return getBootstrapResources(name);
498: }
499: }
500:
501: // get resources of given name from bootstrap loader
502: private Enumeration<URL> getBootstrapResources(String name) {
503: final Enumeration<Resource> e = Launcher
504: .getBootstrapClassPath().getResources(name);
505: return new Enumeration<URL>() {
506: public boolean hasMoreElements() {
507: return e.hasMoreElements();
508: }
509:
510: public URL nextElement() {
511: return ((Resource) e.nextElement()).getURL();
512: }
513: };
514: }
515:
516: // is cl1 ancestor of cl2?
517: private boolean isAncestor(ClassLoader cl1, ClassLoader cl2) {
518: do {
519: cl2 = cl2.getParent();
520: if (cl1 == cl2)
521: return true;
522: } while (cl2 != null);
523: return false;
524: }
525: }
|