org.wicketstuff.nashorn.resource.NashornResource.java Source code

Java tutorial

Introduction

Here is the source code for org.wicketstuff.nashorn.resource.NashornResource.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.wicketstuff.nashorn.resource;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.script.Bindings;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullWriter;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.AbstractResource;
import org.apache.wicket.request.resource.PartWriterCallback;

import jdk.nashorn.api.scripting.ClassFilter;

/**
 * A nashorn resource to execute java script on server side.<br>
 * <br>
 * Please ensure to use the {@link NashornSecurityManager} to restrict class access.
 * 
 * @author Tobias Soloschenko
 *
 */
@SuppressWarnings({ "restriction" })
public class NashornResource extends AbstractResource {

    private static final long serialVersionUID = 1L;

    private ScheduledExecutorService scheduledExecutorService;

    private long delay;

    private TimeUnit delayUnit;

    private long wait;

    private TimeUnit waitUnit;

    private long maxScriptMemorySize;

    /**
     * Creates a new nashorn resource
     * 
     * @param scheduledExecutorService
     *            the scheduled executor service to run scripts
     * @param delay
     *            the delay until a script execution is going to be terminated
     * @param delayUnit
     *            the unit until a script execution is going to be terminated
     * @param wait
     *            how long to w8 until the next memory check occurs
     * @param waitUnit
     *            the unit until the next memory check occurs
     * @param maxScriptMemorySize
     *            the memory usage the script process should use - else it will be aborted
     */
    public NashornResource(ScheduledExecutorService scheduledExecutorService, long delay, TimeUnit delayUnit,
            long wait, TimeUnit waitUnit, long maxScriptMemorySize) {
        this.scheduledExecutorService = scheduledExecutorService;
        this.delay = delay;
        this.delayUnit = delayUnit;
        this.wait = wait;
        this.waitUnit = waitUnit;
        this.maxScriptMemorySize = maxScriptMemorySize;
    }

    /**
     * Executes the java script code received from the client and returns the response
     */
    @Override
    protected ResourceResponse newResourceResponse(Attributes attributes) {
        RequestCycle cycle = RequestCycle.get();
        Long startbyte = cycle.getMetaData(CONTENT_RANGE_STARTBYTE);
        Long endbyte = cycle.getMetaData(CONTENT_RANGE_ENDBYTE);
        HttpServletRequest httpServletRequest = (HttpServletRequest) attributes.getRequest().getContainerRequest();
        try (InputStream inputStream = httpServletRequest.getInputStream()) {
            String script = IOUtils.toString(inputStream);
            if (script.contains("nashornResourceReferenceScriptExecutionThread")) {
                throw new IllegalAccessException(
                        "It is not allowed to gain access to the nashorn thread itself! (nashornResourceReferenceScriptExecutionThread)");
            }
            String safeScript = ensureSafetyScript(script, attributes);
            NashornScriptCallable nashornScriptCallable = new NashornScriptCallable(safeScript, attributes,
                    getClassFilter(), getWriter(), getErrorWriter()) {
                @Override
                protected void setup(Attributes attributes, Bindings bindings) {
                    NashornResource.this.setup(attributes, bindings);
                }
            };
            Object scriptResult = executeScript(nashornScriptCallable, true);
            ResourceResponse resourceResponse = new ResourceResponse();
            resourceResponse.setContentType("text/plain");
            resourceResponse.setWriteCallback(new PartWriterCallback(
                    IOUtils.toInputStream(scriptResult != null ? scriptResult.toString() : ""),
                    Long.valueOf(scriptResult != null ? scriptResult.toString().length() : 0), startbyte, endbyte));
            return resourceResponse;
        } catch (Exception e) {
            ResourceResponse errorResourceResponse = processError(e);
            if (errorResourceResponse == null) {
                throw new WicketRuntimeException("Error while reading / executing the script the script", e);
            } else {
                return errorResourceResponse;
            }

        }
    }

    /**
     * Ensure that the given script is going to be safe. Safe because of endless loops for example.
     * 
     * @param script
     *            the script to be make safe
     * @param attributes
     *            the attributes
     * @return the safe script
     * @throws Exception
     *             if an error occured while making the script safe
     */
    private String ensureSafetyScript(String script, Attributes attributes) throws Exception {
        ClassFilter classFilter = new ClassFilter() {
            @Override
            public boolean exposeToScripts(String arg0) {
                return true;
            }
        };
        NashornScriptCallable nashornScriptCallable = new NashornScriptCallable(
                getScriptByName(NashornResource.class.getSimpleName() + ".js"), attributes, classFilter,
                getWriter(), getErrorWriter()) {
            @Override
            protected void setup(Attributes attributes, Bindings bindings) {
                bindings.put("script", script);
                bindings.put("debug", isDebug());
                bindings.put("debug_log_prefix", NashornResource.class.getSimpleName() + " - ");
            }
        };
        return executeScript(nashornScriptCallable, false).toString();
    }

    /**
     * Gets a script by name - the scope is always the class NashornResource
     * 
     * @param name
     *            the name of the script
     * @return the script
     * @throws IOException
     *             if the script fail to load
     */
    private String getScriptByName(String name) throws IOException {
        String script = "";

        try (InputStream scriptInputStream = NashornResource.class.getResourceAsStream(name)) {
            script = IOUtils.toString(scriptInputStream);
        }
        return script;
    }

    /**
     * Executes a given script callable and the corresponding script
     * 
     * @param executeScript
     *            the script callable to execute
     * @param watch
     *            if the script execution should be watched
     * @return the script result
     * @throws Exception
     */
    private Object executeScript(NashornScriptCallable executeScript, boolean watch) throws Exception {
        Future<Object> scriptTask = scheduledExecutorService.submit(executeScript);
        if (watch && waitUnit != null) {
            scheduledExecutorService.execute(new NashornMemoryWatcher(executeScript, scriptTask, wait, waitUnit,
                    maxScriptMemorySize, isDebug(), getErrorWriter()));
        }
        scheduledExecutorService.schedule(() -> {
            scriptTask.cancel(true);
        }, this.delay, this.delayUnit);
        return scriptTask.get();
    }

    /**
     * Customize the error response sent to the client
     * 
     * @param e
     *            the exception occurred
     * @return the error response
     */
    protected ResourceResponse processError(Exception e) {
        return null;
    }

    /**
     * Setup the bindings and make information available to the scripting context
     * 
     * @param attributes
     *            the attributes of the request
     * @param bindings
     *            the bindings to add java objects to
     */
    protected void setup(Attributes attributes, Bindings bindings) {
        // NOOP
    }

    /**
     * Gets the class filter to apply to the scripting engine
     * 
     * @return the class filter to apply to the scripting engine
     */
    protected ClassFilter getClassFilter() {
        // default is to allow nothing!
        return new ClassFilter() {
            @Override
            public boolean exposeToScripts(String name) {
                return false;
            }
        };
    }

    /**
     * Gets the writer to which print outputs are going to be written to
     * 
     * the default is to use {@link NullWriter}
     * 
     * @return the writer for output
     */
    protected Writer getWriter() {
        return new NullWriter();
    }

    /**
     * Gets the writer to which error messages are going to be written to
     * 
     * the default is to use {@link NullWriter}
     * 
     * @return the error writer
     */
    protected Writer getErrorWriter() {
        return new NullWriter();
    }

    /**
     * If debug is enabled
     * 
     * @return if debug is enabled
     */
    protected boolean isDebug() {
        return false;
    }
}