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                    @Override
083                    public RhinoExecutor invoke()
084                    {
085                        final Context context = contextFactory.enterContext();
086
087                        final ScriptableObject scope = context.initStandardObjects();
088
089                        try
090                        {
091                            context.setOptimizationLevel(-1);
092
093                            for (Resource script : scripts)
094                            {
095                                loadScript(context, scope, script);
096                            }
097
098                        } finally
099                        {
100                            Context.exit();
101                        }
102
103                        return new RhinoExecutor()
104                        {
105                            @Override
106                            public ScriptableObject invokeFunction(String functionName, Object... arguments)
107                            {
108                                contextFactory.enterContext(context);
109
110                                try
111                                {
112                                    NativeFunction function = (NativeFunction) scope.get(functionName, scope);
113
114                                    return (ScriptableObject) function.call(context, scope, null, arguments);
115                                } finally
116                                {
117                                    Context.exit();
118                                }
119                            }
120
121                            @Override
122                            public void discard()
123                            {
124                                put(this);
125                            }
126                        };
127                    }
128                });
129    }
130
131    private void loadScript(final Context context, final ScriptableObject scope, final Resource script)
132    {
133        tracker.run(String.format("Loading script %s.", script),
134                new Runnable()
135                {
136                    @Override
137                    public void run()
138                    {
139                        InputStream in = null;
140                        Reader r = null;
141
142                        try
143                        {
144                            in = script.openStream();
145                            r = new InputStreamReader(in);
146
147                            context.evaluateReader(scope, r, script.toString(), 1, null);
148                        } catch (IOException ex)
149                        {
150                            throw new RuntimeException(String.format("Unable to read script %s: %s",
151                                    script,
152                                    ExceptionUtils.toMessage(ex)
153                            ), ex);
154                        } finally
155                        {
156                            InternalUtils.close(r);
157                            InternalUtils.close(in);
158                        }
159                    }
160                });
161
162    }
163
164
165}