org.forgerock.openidm.script.impl.ScriptRegistryService.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openidm.script.impl.ScriptRegistryService.java

Source

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2015 ForgeRock AS. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://forgerock.org/license/CDDLv1.0.html
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at http://forgerock.org/license/CDDLv1.0.html
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 */

package org.forgerock.openidm.script.impl;

import static org.forgerock.json.resource.Responses.newActionResponse;
import static org.forgerock.util.promise.Promises.newResultPromise;

import java.io.File;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.xml.bind.DatatypeConverter;

import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.References;
import org.apache.felix.scr.annotations.Service;
import org.forgerock.audit.events.AuditEvent;
import org.forgerock.openidm.router.IDMConnectionFactory;
import org.forgerock.openidm.script.ResourceFunctions;
import org.forgerock.services.context.Context;
import org.forgerock.json.JsonValue;
import org.forgerock.json.JsonValueException;
import org.forgerock.json.crypto.JsonCrypto;
import org.forgerock.json.crypto.JsonCryptoException;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ConnectionFactory;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.DeleteRequest;
import org.forgerock.json.resource.ForbiddenException;
import org.forgerock.json.resource.InternalServerErrorException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchRequest;
import org.forgerock.json.resource.QueryRequest;
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.Requests;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ServiceUnavailableException;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.openidm.config.enhanced.EnhancedConfig;
import org.forgerock.openidm.core.IdentityServer;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.openidm.crypto.CryptoService;
import org.forgerock.openidm.quartz.impl.ExecutionException;
import org.forgerock.openidm.quartz.impl.ScheduledService;
import org.forgerock.script.Script;
import org.forgerock.script.ScriptEntry;
import org.forgerock.script.engine.ScriptEngineFactory;
import org.forgerock.script.exception.ScriptCompilationException;
import org.forgerock.script.exception.ScriptThrownException;
import org.forgerock.script.registry.ScriptRegistryImpl;
import org.forgerock.script.scope.Function;
import org.forgerock.script.scope.FunctionFactory;
import org.forgerock.script.scope.Parameter;
import org.forgerock.script.source.DirectoryContainer;
import org.forgerock.script.source.SourceUnit;
import org.forgerock.util.promise.Promise;
import org.ops4j.pax.swissbox.extender.BundleWatcher;
import org.ops4j.pax.swissbox.extender.ManifestEntry;
import org.osgi.framework.Constants;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 */
@Component(name = ScriptRegistryService.PID, policy = ConfigurationPolicy.REQUIRE, metatype = true, description = "OpenIDM Script Registry Service", immediate = true)
@Service
@Properties({ @Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME),
        @Property(name = Constants.SERVICE_DESCRIPTION, value = "OpenIDM Script Registry Service"),
        @Property(name = ServerConstants.ROUTER_PREFIX, value = "/script*") })
@References({
        @Reference(name = "CryptoServiceReference", referenceInterface = CryptoService.class, bind = "bindCryptoService", unbind = "unbindCryptoService", cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC),
        @Reference(name = "IDMConnectionFactoryReference", referenceInterface = IDMConnectionFactory.class, bind = "setConnectionFactory", unbind = "unsetConnectionFactory", cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC),
        @Reference(name = "ScriptEngineFactoryReference", referenceInterface = ScriptEngineFactory.class, bind = "addingEntries", unbind = "removingEntries", cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC),
        @Reference(name = "FunctionReference", referenceInterface = Function.class, bind = "bindFunction", unbind = "unbindFunction", cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, target = "("
                + ScriptRegistryService.SCRIPT_NAME + "=*)") })
public class ScriptRegistryService extends ScriptRegistryImpl implements RequestHandler, ScheduledService {

    public static final Set<String> reservedNames;

    static {
        Set<String> _reservedNames = new HashSet<String>(15);
        _reservedNames.add("create");
        _reservedNames.add("read");
        _reservedNames.add("update");
        _reservedNames.add("patch");
        _reservedNames.add("query");
        _reservedNames.add("delete");
        _reservedNames.add("action");
        _reservedNames.add("encrypt");
        _reservedNames.add("decrypt");
        _reservedNames.add("isEncrypted");
        _reservedNames.add("isHashed");
        _reservedNames.add("matches");

        for (IdentityServerFunctions f : IdentityServerFunctions.values()) {
            _reservedNames.add(f.name());
        }
        reservedNames = Collections.unmodifiableSet(_reservedNames);
    }

    // TODO Move to public package
    public static final String SCRIPT_NAME = "org.forgerock.openidm.script.name";

    // Public Constants
    public static final String PID = "org.forgerock.openidm.script";

    private ConnectionFactory connectionFactory = null;

    /**
     * Setup logging for the {@link ScriptRegistryService}.
     */
    // private static final LocalizedLogger logger =
    // LocalizedLogger.getLocalizedLogger(ScriptRegistryService.class);
    private static final Logger logger = LoggerFactory.getLogger(ScriptRegistryService.class);
    private static final String PROP_IDENTITY_SERVER = "identityServer";
    private static final String PROP_OPENIDM = "openidm";
    private static final String PROP_CONSOLE = "console";

    private static final String SOURCE_DIRECTORY = "directory";
    private static final String SOURCE_FILE = "file";
    private static final String SOURCE_SUBDIRECTORIES = "subdirectories";
    private static final String SOURCE_VISIBILITY = "visibility";
    private static final String SOURCE_TYPE = "type";
    private static final String SOURCE_GLOBALS = "globals";

    /** Enhanced configuration service. */
    @Reference(policy = ReferencePolicy.DYNAMIC)
    private EnhancedConfig enhancedConfig;

    private final ConcurrentMap<String, Object> openidm = new ConcurrentHashMap<String, Object>();
    private static final ConcurrentMap<String, Object> propertiesCache = new ConcurrentHashMap<String, Object>();

    private enum Action {
        compile, eval
    }

    private BundleWatcher<ManifestEntry> manifestWatcher;

    @Activate
    protected void activate(ComponentContext context) throws Exception {
        JsonValue configuration = enhancedConfig.getConfigurationAsJson(context);

        setConfiguration(configuration.required().asMap());

        HashMap<String, Object> identityServer = new HashMap<String, Object>();
        for (IdentityServerFunctions f : IdentityServerFunctions.values()) {
            identityServer.put(f.name(), f);
        }
        Map<String, Object> console = new HashMap<String, Object>();
        console.put("log", new Function<Void>() {
            @Override
            public Void call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length > 0) {
                    if (arguments[0] instanceof String) {
                        System.out.println((String) arguments[0]);
                    }
                }
                return null;
            }
        });
        put(PROP_IDENTITY_SERVER, identityServer);
        put(PROP_CONSOLE, console);
        put(PROP_OPENIDM, openidm);
        JsonValue properties = configuration.get("properties");
        if (properties.isMap()) {
            for (Map.Entry<String, Object> entry : properties.asMap().entrySet()) {
                if (PROP_IDENTITY_SERVER.equals(entry.getKey()) || PROP_OPENIDM.equals(entry.getKey())
                        || PROP_CONSOLE.equals(entry.getKey())) {
                    continue;
                }
                put(entry.getKey(), entry.getValue());
            }
        }

        try {
            JsonValue sources = configuration.get("sources");
            if (!sources.isNull()) {
                // Must reverse default-first config since ScriptRegistryImpl loads scripts from the first dir it hits.
                List<String> keys = new ArrayList(sources.keys());
                Collections.reverse(keys);

                for (String key : keys) {
                    JsonValue source = sources.get(key);
                    String directory = source.get(SOURCE_DIRECTORY).asString();
                    URL directoryURL = (new File(directory)).toURI().toURL();
                    /* TODO: Support addition config properties (currently set to defaults in commons)
                    JsonValue subDirValue = source.get(SOURCE_SUBDIRECTORIES).defaultTo("auto-true");
                    boolean subdirectories = true;
                    if (subDirValue.isBoolean()) {
                    subdirectories = subDirValue.asBoolean();
                    } else {
                    subdirectories = Boolean.parseBoolean(subDirValue.asString());
                    }
                    String type = source.get(SOURCE_TYPE).defaultTo("auto-detect").asString();
                    String visibility = source.get(SOURCE_VISIBILITY).defaultTo("public").asString();
                    */
                    DirectoryContainer dc = new DirectoryContainer(key, directoryURL);
                    addSourceUnit(dc);
                }
            }
        } catch (Exception e) {
            logger.error("Error loading sources", e);
            throw e;
        }

        // OPENIDM-1746 Set the script registry class loader to this class, so that any libraries
        // bundled as an OSGI-Fragment attached to this bundle will be "seen" by scripts.
        setRegistryLevelScriptClassLoader(this.getClass().getClassLoader());

        /*
         * manifestWatcher = new BundleWatcher<ManifestEntry>(context, new
         * ScriptEngineManifestScanner(), null); manifestWatcher.start();
         */

        logger.info("OpenIDM Script Service component is activated.");
    }

    @Modified
    protected void modified(ComponentContext context) {
        JsonValue configuration = enhancedConfig.getConfigurationAsJson(context);
        setConfiguration(configuration.required().asMap());
        propertiesCache.clear();
        Set<String> keys = null != getBindings() ? new HashSet<String>(getBindings().keySet())
                : Collections.<String>emptySet();
        keys.remove(PROP_OPENIDM);
        keys.remove(PROP_IDENTITY_SERVER);
        keys.remove(PROP_CONSOLE);
        JsonValue properties = configuration.get("properties");
        if (properties.isMap()) {
            for (Map.Entry<String, Object> entry : properties.asMap().entrySet()) {
                if (PROP_IDENTITY_SERVER.equals(entry.getKey()) || PROP_OPENIDM.equals(entry.getKey())
                        || PROP_CONSOLE.equals(entry.getKey())) {
                    continue;
                }
                put(entry.getKey(), entry.getValue());
                keys.remove(entry.getKey());
            }
        }
        if (!keys.isEmpty()) {
            for (String name : keys) {
                getBindings().remove(name);
            }
        }
        logger.info("OpenIDM Script Service component is modified.");
    }

    @Deactivate
    protected void deactivate(ComponentContext context) {
        if (null != manifestWatcher) {
            manifestWatcher.stop();
        }
        propertiesCache.clear();
        openidm.clear();
        setBindings(null);
        logger.info("OpenIDM Script Service component is deactivated.");
    }

    public void setConnectionFactory(IDMConnectionFactory connectionFactory) {
        openidm.put("create", ResourceFunctions.newCreateFunction(connectionFactory));
        openidm.put("read", ResourceFunctions.newReadFunction(connectionFactory));
        openidm.put("update", ResourceFunctions.newUpdateFunction(connectionFactory));
        openidm.put("patch", ResourceFunctions.newPatchFunction(connectionFactory));
        openidm.put("query", ResourceFunctions.newQueryFunction(connectionFactory));
        openidm.put("delete", ResourceFunctions.newDeleteFunction(connectionFactory));
        openidm.put("action", ResourceFunctions.newActionFunction(connectionFactory));
        this.connectionFactory = connectionFactory;
        logger.info("Resource functions are enabled");
    }

    public void unsetConnectionFactory(IDMConnectionFactory connectionFactory) {
        openidm.remove("create");
        openidm.remove("read");
        openidm.remove("update");
        openidm.remove("patch");
        openidm.remove("query");
        openidm.remove("delete");
        openidm.remove("action");
        this.connectionFactory = null;
        logger.info("Resource functions are disabled");
    }

    protected void bindCryptoService(final CryptoService cryptoService) {
        // hash(any value, string algorithm)
        openidm.put("hash", new Function<JsonValue>() {

            static final long serialVersionUID = 1L;

            public JsonValue call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length == 2) {
                    JsonValue value = null;
                    String algorithm = null;
                    if (arguments[0] instanceof Map || arguments[0] instanceof List
                            || arguments[0] instanceof String || arguments[0] instanceof Number
                            || arguments[0] instanceof Boolean) {
                        value = new JsonValue(arguments[0]);
                    } else if (arguments[0] instanceof JsonValue) {
                        value = (JsonValue) arguments[0];
                    } else {
                        throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("hash", arguments));
                    }
                    if (arguments[1] instanceof String) {
                        algorithm = (String) arguments[1];
                    } else if (arguments[1] == null) {
                        algorithm = ServerConstants.SECURITY_CRYPTOGRAPHY_DEFAULT_HASHING_ALGORITHM;
                    } else {
                        throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("hash", arguments));
                    }

                    try {
                        return cryptoService.hash(value, algorithm);
                    } catch (JsonCryptoException e) {
                        throw new InternalServerErrorException(e.getMessage(), e);
                    }
                } else {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("hash", arguments));
                }
            }
        });
        // encrypt(any value, string cipher, string alias)
        openidm.put("encrypt", new Function<JsonValue>() {

            static final long serialVersionUID = 1L;

            public JsonValue call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length == 3) {
                    JsonValue value = null;
                    String cipher = null;
                    String alias = null;
                    if (arguments[0] instanceof Map || arguments[0] instanceof List
                            || arguments[0] instanceof String || arguments[0] instanceof Number
                            || arguments[0] instanceof Boolean) {
                        value = new JsonValue(arguments[0]);
                    } else if (arguments[0] instanceof JsonValue) {
                        value = (JsonValue) arguments[0];
                    } else {
                        throw new NoSuchMethodException(
                                FunctionFactory.getNoSuchMethodMessage("encrypt", arguments));
                    }
                    if (arguments[1] instanceof String) {
                        cipher = (String) arguments[1];
                    } else if (arguments[1] == null) {
                        cipher = ServerConstants.SECURITY_CRYPTOGRAPHY_DEFAULT_CIPHER;
                    } else {
                        throw new NoSuchMethodException(
                                FunctionFactory.getNoSuchMethodMessage("encrypt", arguments));
                    }

                    if (arguments[2] instanceof String) {
                        alias = (String) arguments[2];
                    } else {
                        throw new NoSuchMethodException(
                                FunctionFactory.getNoSuchMethodMessage("encrypt", arguments));
                    }
                    try {
                        return cryptoService.encrypt(value, cipher, alias);
                    } catch (JsonCryptoException e) {
                        throw new InternalServerErrorException(e.getMessage(), e);
                    }
                } else {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("encrypt", arguments));
                }
            }
        });
        // decrypt(any value)
        openidm.put("decrypt", new Function<JsonValue>() {

            static final long serialVersionUID = 1L;

            public JsonValue call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length == 1 && (arguments[0] instanceof Map || arguments[0] instanceof JsonValue)) {
                    return cryptoService.decrypt(arguments[0] instanceof JsonValue ? (JsonValue) arguments[0]
                            : new JsonValue(arguments[0]));
                } else {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("decrypt", arguments));
                }
            }
        });
        // isEncrypted(any value)
        openidm.put("isEncrypted", new Function<Boolean>() {

            static final long serialVersionUID = 1L;

            public Boolean call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments == null || arguments.length == 0) {
                    return false;
                } else if (arguments.length == 1) {
                    return JsonCrypto.isJsonCrypto(arguments[0] instanceof JsonValue ? (JsonValue) arguments[0]
                            : new JsonValue(arguments[0]));
                } else {
                    throw new NoSuchMethodException(
                            FunctionFactory.getNoSuchMethodMessage("isEncrypted", arguments));
                }
            }
        });
        // isHashed(any value)
        openidm.put("isHashed", new Function<Boolean>() {

            static final long serialVersionUID = 1L;

            public Boolean call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments == null || arguments.length == 0) {
                    return false;
                } else if (arguments.length == 1) {
                    return cryptoService.isHashed(arguments[0] instanceof JsonValue ? (JsonValue) arguments[0]
                            : new JsonValue(arguments[0]));
                } else {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("isHashed", arguments));
                }
            }
        });
        // matches(String plaintext, any value)
        openidm.put("matches", new Function<Boolean>() {

            static final long serialVersionUID = 1L;

            public Boolean call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments == null || arguments.length == 0 || arguments.length == 1) {
                    return false;
                } else if (arguments.length == 2) {
                    try {
                        return cryptoService.matches(arguments[0].toString(),
                                arguments[1] instanceof JsonValue ? (JsonValue) arguments[1]
                                        : new JsonValue(arguments[1]));
                    } catch (JsonCryptoException e) {
                        throw new InternalServerErrorException(e.getMessage(), e);
                    }
                } else {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage("matches", arguments));
                }
            }
        });
        logger.info("Crypto functions are enabled");
    }

    protected void unbindCryptoService(final CryptoService function) {
        openidm.remove("encrypt");
        openidm.remove("decrypt");
        openidm.remove("isEncrypted");
        openidm.remove("isHashed");
        openidm.remove("matches");
        logger.info("Crypto functions are disabled");
    }

    protected void bindFunction(final Function function, Map properties) {
        Object name = properties.get(SCRIPT_NAME);
        if (name instanceof String && StringUtils.isNotBlank((String) name)
                && !reservedNames.contains((String) name)) {
            openidm.put((String) name, function);
            logger.info("openidm.{} function is enabled", name);
        }
    }

    protected void unbindFunction(final Function function, Map properties) {
        Object name = properties.get(SCRIPT_NAME);
        if (name instanceof String && StringUtils.isNotBlank((String) name)
                && !reservedNames.contains((String) name)) {
            openidm.remove(name, function);
            logger.info("openidm.{} function is disabled", name);
        }
    }

    @Override
    public ScriptEntry takeScript(JsonValue script) throws ScriptException {
        JsonValue scriptConfig = script.clone();
        if (scriptConfig.get(SourceUnit.ATTR_NAME).isNull()) {
            JsonValue file = scriptConfig.get(SOURCE_FILE);
            JsonValue source = scriptConfig.get(SourceUnit.ATTR_SOURCE);

            if (!file.isNull()) {
                // If we do not have a name use the file.
                scriptConfig.put(SourceUnit.ATTR_NAME, file.asString());
            } else if (!source.isNull()) {
                /*
                 * We assign a name here otherwise ScriptRegistryImpl.takeScript() assigns a random UUID.
                 * This results in a newly created and cached class on every invocation.
                 */
                try {
                    MessageDigest md = MessageDigest.getInstance("SHA-1");

                    // digest source AND type as we could have identical source for different types
                    String type = scriptConfig.get(SourceUnit.ATTR_TYPE).asString();
                    byte[] nameAndType = (source.asString() + type).getBytes();
                    byte[] digest = md.digest(nameAndType);
                    String name = DatatypeConverter.printHexBinary(digest);

                    scriptConfig.put(SourceUnit.ATTR_NAME, name);
                } catch (NoSuchAlgorithmException e) {
                    // SHA-1 is a required implementation. This should never happen.
                    logger.error(
                            "Could not get SHA-1 MessageDigest instance. This should be implemented on any standard JVM.",
                            e);
                }
            }
        }

        // Get and remove any defined globals
        JsonValue globals = scriptConfig.get(SOURCE_GLOBALS);
        scriptConfig.remove(SOURCE_GLOBALS);

        // Create the script entry
        ScriptEntry scriptEntry = super.takeScript(scriptConfig);

        // Add the globals (if any) to the script bindings
        if (!globals.isNull() && globals.isMap()) {
            for (String key : globals.keys()) {
                scriptEntry.put(key, globals.get(key));
            }
        }
        return scriptEntry;
    }

    private static enum IdentityServerFunctions implements Function<Object> {
        getProperty {
            public Object call(Parameter scope, Function<?> callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                boolean useCache = false;
                if (arguments.length < 1 || arguments.length > 3) {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage(this.name(), arguments));
                }
                if (arguments.length == 3) {
                    useCache = (Boolean) arguments[2];
                }
                if (arguments[0] instanceof String) {
                    String name = (String) arguments[0];
                    Object result = null;
                    if (useCache) {
                        result = propertiesCache.get(name);
                    }
                    if (null == result) {
                        Object defaultValue = arguments.length == 2 ? arguments[1] : null;
                        result = IdentityServer.getInstance().getProperty(name, defaultValue, Object.class);
                        propertiesCache.putIfAbsent(name, result);
                    }
                    return result;
                } else {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage(this.name(), arguments));
                }
            }
        },

        getWorkingLocation {
            public Object call(Parameter scope, Function callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length != 0) {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage(this.name(), arguments));
                }
                return IdentityServer.getInstance().getWorkingLocation().getAbsolutePath();
            }
        },

        getProjectLocation {
            public Object call(Parameter scope, Function callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length != 0) {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage(this.name(), arguments));
                }
                return IdentityServer.getInstance().getProjectLocation().getAbsolutePath();
            }
        },
        getInstallLocation {
            public Object call(Parameter scope, Function callback, Object... arguments)
                    throws ResourceException, NoSuchMethodException {
                if (arguments.length != 0) {
                    throw new NoSuchMethodException(FunctionFactory.getNoSuchMethodMessage(this.name(), arguments));
                }
                return IdentityServer.getInstance().getInstallLocation().getAbsolutePath();
            }
        };
        static final long serialVersionUID = 1L;
    }

    private boolean isSourceUnit(String name) {
        if (SourceUnit.ATTR_NAME.equals(name) || SourceUnit.ATTR_REVISION.equals(name)
                || SourceUnit.ATTR_SOURCE.equals(name) || SourceUnit.ATTR_TYPE.equals(name)
                || SourceUnit.ATTR_VISIBILITY.equals(name) || SourceUnit.AUTO_DETECT.equals(name)
                || SOURCE_FILE.equals(name) || SOURCE_GLOBALS.equals(name)) {
            return true;
        }
        return false;
    }

    // ----- Implementation of RequestHandler interface

    public Promise<ActionResponse, ResourceException> handleAction(final Context context,
            final ActionRequest request) {
        String resourcePath = request.getResourcePath();
        JsonValue content = request.getContent();
        Map<String, Object> bindings = new HashMap<String, Object>();
        JsonValue config = new JsonValue(new HashMap<String, Object>());
        ScriptEntry scriptEntry = null;
        try {
            if (resourcePath == null || "".equals(resourcePath)) {
                for (String key : content.keys()) {
                    if (isSourceUnit(key)) {
                        config.put(key, content.get(key).getObject());
                    } else {
                        bindings.put(key, content.get(key).getObject());
                    }
                }
                // The script will be in the request content
                scriptEntry = takeScript(config);
                // Add any additional parameters to the map of bindings
                bindings.putAll(request.getAdditionalParameters());
            } else {
                throw new NotSupportedException("Actions are not supported for resource instances");
            }
            switch (request.getActionAsEnum(Action.class)) {
            case compile:
                if (scriptEntry.isActive()) {
                    // just get the script - compilation technically happened above in takeScript
                    scriptEntry.getScript(context);
                    return newActionResponse(new JsonValue(true)).asPromise();
                } else {
                    throw new ServiceUnavailableException();
                }
            case eval:
                if (scriptEntry.isActive()) {
                    Script script = scriptEntry.getScript(context);
                    return newResultPromise(
                            newActionResponse(new JsonValue(script.eval(new SimpleBindings(bindings)))));
                } else {
                    throw new ServiceUnavailableException();
                }
            default:
                throw new BadRequestException("Unrecognized action ID " + request.getAction());
            }
        } catch (ResourceException e) {
            return e.asPromise();
        } catch (ScriptCompilationException e) {
            return new BadRequestException(e.getMessage(), e).asPromise();
        } catch (IllegalArgumentException e) { // from getActionAsEnum
            return new BadRequestException(e.getMessage(), e).asPromise();
        } catch (Exception e) {
            return new InternalServerErrorException(e.getMessage(), e).asPromise();
        }
    }

    public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request,
            final QueryResourceHandler handler) {
        final ResourceException e = new NotSupportedException("Query operations are not supported");
        return e.asPromise();
    }

    public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
            final ReadRequest request) {
        final ResourceException e = new NotSupportedException("Read operations are not supported");
        return e.asPromise();
    }

    public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
            final CreateRequest request) {
        final ResourceException e = new NotSupportedException("Create operations are not supported");
        return e.asPromise();
    }

    public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
            final DeleteRequest request) {
        final ResourceException e = new NotSupportedException("Delete operations are not supported");
        return e.asPromise();
    }

    public Promise<ResourceResponse, ResourceException> handlePatch(final Context context,
            final PatchRequest request) {
        final ResourceException e = new NotSupportedException("Patch operations are not supported");
        return e.asPromise();
    }

    public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
            final UpdateRequest request) {
        final ResourceException e = new NotSupportedException("Update operations are not supported");
        return e.asPromise();
    }

    @Override
    public void execute(Context context, Map<String, Object> scheduledContext) throws ExecutionException {

        try {
            String scriptName = (String) scheduledContext.get(CONFIG_NAME);
            JsonValue params = new JsonValue(scheduledContext).get(CONFIGURED_INVOKE_CONTEXT);
            JsonValue scriptValue = params.get("script").expect(Map.class).clone();

            if (scriptValue.get(SourceUnit.ATTR_NAME).isNull()) {
                if (!scriptValue.get(SOURCE_FILE).isNull()) {
                    scriptValue.put(SourceUnit.ATTR_NAME, scriptValue.get(SOURCE_FILE).getObject());
                }
            }

            if (!scriptValue.isNull()) {
                ScriptEntry entry = takeScript(scriptValue);
                JsonValue input = params.get("input");
                execScript(context, entry, input);
            } else {
                throw new ExecutionException("No valid script '" + scriptName + "' configured in schedule.");
            }
        } catch (JsonValueException jve) {
            throw new ExecutionException(jve);
        } catch (ScriptException e) {
            throw new ExecutionException(e);
        } catch (ResourceException e) {
            throw new ExecutionException(e);
        }
    }

    @Override
    public void auditScheduledService(final Context context, final AuditEvent auditEvent)
            throws ExecutionException {
        try {
            if (connectionFactory != null) {
                connectionFactory.getConnection().create(context,
                        Requests.newCreateRequest("audit/access", auditEvent.getValue()));
            }
        } catch (ResourceException e) {
            logger.error("Unable to audit scheduled service {}", auditEvent.toString());
            throw new ExecutionException("Unable to audit scheduled service", e);
        }
    }

    private void execScript(Context context, ScriptEntry script, JsonValue value)
            throws ForbiddenException, InternalServerErrorException {
        if (null != script && script.isActive()) {
            Script executable = script.getScript(context);
            executable.put("object", value.getObject());
            try {
                executable.eval(); // allows direct modification to the object
            } catch (ScriptThrownException ste) {
                throw new ForbiddenException(ste.getValue().toString());
            } catch (ScriptException se) {
                throw new InternalServerErrorException("script encountered exception", se);
            }
        }
    }
}