001// Copyright 2013-2014 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.webresources;
016
017import org.apache.tapestry5.ioc.Invokable;
018import org.apache.tapestry5.ioc.OperationTracker;
019import org.apache.tapestry5.ioc.Resource;
020import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
021import org.apache.tapestry5.ioc.internal.util.InternalUtils;
022import org.apache.tapestry5.ioc.util.ExceptionUtils;
023import org.apache.tapestry5.ioc.util.Stack;
024import org.mozilla.javascript.Context;
025import org.mozilla.javascript.ContextFactory;
026import org.mozilla.javascript.NativeFunction;
027import org.mozilla.javascript.ScriptableObject;
028
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.InputStreamReader;
032import java.io.Reader;
033import java.util.List;
034
035/**
036 * Manages a pool of initialized {@link RhinoExecutor} instances.  The instances are initialized for a particular
037 */
038public class RhinoExecutorPool
039{
040    private final OperationTracker tracker;
041
042    private final List<Resource> scripts;
043
044    private final Stack<RhinoExecutor> executors = CollectionFactory.newStack();
045
046    private final ContextFactory contextFactory = new ContextFactory();
047
048    public RhinoExecutorPool(OperationTracker tracker, List<Resource> scripts)
049    {
050        this.tracker = tracker;
051        this.scripts = scripts;
052    }
053
054    /**
055     * Gets or creates an available executor. It is expected that {@link #put(RhinoExecutor)} will
056     * be invoked after the executor completes.
057     *
058     * @return executor
059     */
060    public synchronized RhinoExecutor get()
061    {
062
063        if (executors.isEmpty())
064        {
065            return createExecutor();
066        }
067
068        return executors.pop();
069    }
070
071    private synchronized void put(RhinoExecutor executor)
072    {
073        executors.push(executor);
074    }
075
076    private RhinoExecutor createExecutor()
077    {
078        return tracker.invoke(String.format("Creating Rhino executor for source(s) %s.",
079                InternalUtils.join(scripts)),
080                new Invokable<RhinoExecutor>()
081                {
082                    public RhinoExecutor invoke()
083                    {
084                        final Context context = contextFactory.enterContext();
085
086                        final ScriptableObject scope = context.initStandardObjects();
087
088                        try
089                        {
090                            context.setOptimizationLevel(-1);
091
092                            for (Resource script : scripts)
093                            {
094                                loadScript(context, scope, script);
095                            }
096
097                        } finally
098                        {
099                            Context.exit();
100                        }
101
102                        return new RhinoExecutor()
103                        {
104                            public ScriptableObject invokeFunction(String functionName, Object... arguments)
105                            {
106                                contextFactory.enterContext(context);
107
108                                try
109                                {
110                                    NativeFunction function = (NativeFunction) scope.get(functionName, scope);
111
112                                    return (ScriptableObject) function.call(context, scope, null, arguments);
113                                } finally
114                                {
115                                    Context.exit();
116                                }
117                            }
118
119                            public void discard()
120                            {
121                                put(this);
122                            }
123                        };
124                    }
125                });
126    }
127
128    private void loadScript(final Context context, final ScriptableObject scope, final Resource script)
129    {
130        tracker.run(String.format("Loading script %s.", script),
131                new Runnable()
132                {
133                    public void run()
134                    {
135                        InputStream in = null;
136                        Reader r = null;
137
138                        try
139                        {
140                            in = script.openStream();
141                            r = new InputStreamReader(in);
142
143                            context.evaluateReader(scope, r, script.toString(), 1, null);
144                        } catch (IOException ex)
145                        {
146                            throw new RuntimeException(String.format("Unable to read script %s: %s",
147                                    script,
148                                    ExceptionUtils.toMessage(ex)
149                            ), ex);
150                        } finally
151                        {
152                            InternalUtils.close(r);
153                            InternalUtils.close(in);
154                        }
155                    }
156                });
157
158    }
159
160
161}