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.InternalUtils;
021import org.apache.tapestry5.ioc.util.ExceptionUtils;
022import org.mozilla.javascript.Context;
023import org.mozilla.javascript.ContextFactory;
024import org.mozilla.javascript.NativeFunction;
025import org.mozilla.javascript.ScriptableObject;
026
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.InputStreamReader;
030import java.io.Reader;
031import java.util.List;
032import java.util.Queue;
033import java.util.concurrent.ConcurrentLinkedQueue;
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 Queue<RhinoExecutor> executors = new ConcurrentLinkedQueue<RhinoExecutor>();
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 RhinoExecutor get()
061    {
062
063        RhinoExecutor executor = executors.poll();
064        if (executor != null)
065        {
066            return executor;
067        }
068
069        return createExecutor();
070    }
071
072    private void put(RhinoExecutor executor)
073    {
074        executors.add(executor);
075    }
076
077    private RhinoExecutor createExecutor()
078    {
079        return tracker.invoke(String.format("Creating Rhino executor for source(s) %s.",
080                InternalUtils.join(scripts)),
081                new Invokable<RhinoExecutor>()
082                {
083                    @Override
084                    public RhinoExecutor invoke()
085                    {
086                        final Context context = contextFactory.enterContext();
087
088                        final ScriptableObject scope = context.initStandardObjects();
089
090                        try
091                        {
092                            context.setOptimizationLevel(-1);
093
094                            for (Resource script : scripts)
095                            {
096                                loadScript(context, scope, script);
097                            }
098
099                        } finally
100                        {
101                            Context.exit();
102                        }
103
104                        return new RhinoExecutor()
105                        {
106                            @Override
107                            public ScriptableObject invokeFunction(String functionName, Object... arguments)
108                            {
109                                contextFactory.enterContext(context);
110
111                                try
112                                {
113                                    NativeFunction function = (NativeFunction) scope.get(functionName, scope);
114
115                                    return (ScriptableObject) function.call(context, scope, null, arguments);
116                                } finally
117                                {
118                                    Context.exit();
119                                }
120                            }
121
122                            @Override
123                            public void discard()
124                            {
125                                put(this);
126                            }
127                        };
128                    }
129                });
130    }
131
132    private void loadScript(final Context context, final ScriptableObject scope, final Resource script)
133    {
134        tracker.run(String.format("Loading script %s.", script),
135                new Runnable()
136                {
137                    @Override
138                    public void run()
139                    {
140                        InputStream in = null;
141                        Reader r = null;
142
143                        try
144                        {
145                            in = script.openStream();
146                            r = new InputStreamReader(in);
147
148                            context.evaluateReader(scope, r, script.toString(), 1, null);
149                        } catch (IOException ex)
150                        {
151                            throw new RuntimeException(String.format("Unable to read script %s: %s",
152                                    script,
153                                    ExceptionUtils.toMessage(ex)
154                            ), ex);
155                        } finally
156                        {
157                            InternalUtils.close(r);
158                            InternalUtils.close(in);
159                        }
160                    }
161                });
162
163    }
164
165
166}