    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, "UTF-8");
    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}