Java tutorial
/* * 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 legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. 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]". * * Copyright 2011 ForgeRock AS. All rights reserved. */ package org.forgerock.openidm.script.javascript; // Java Standard Edition import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; // JSON-Fluent import org.apache.commons.codec.binary.StringUtils; import org.forgerock.json.fluent.JsonValue; import org.forgerock.json.fluent.JsonValueException; // OpenIDM import org.forgerock.openidm.core.IdentityServer; import org.forgerock.openidm.script.Script; import org.forgerock.openidm.script.ScriptException; import org.forgerock.openidm.script.ScriptFactory; import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of a script factory for JavaScript. * <p/> * Expects {@code "type"} configuration property of {@code "text/javascript"} and * {@code "source"} property, which contains the script source code. * <p/> * The optional boolean property {@code "sharedScope"} indicates if a shared scope should be * used. If {@code true}, a sealed shared scope containing standard JavaScript objects * (Object, String, Number, Date, etc.) will be used for script execution rather than * allocating a new unsealed scope for each execution. * * @author Paul C. Bryan */ public class JavaScriptFactory implements ScriptFactory { //Logger private static final Logger logger = LoggerFactory.getLogger(JavaScriptFactory.class); /** * The JavaScript file extension<br> * <br> * Value is <code>js</code> */ public static final String JS_EXTENSION = ".js"; private ContextFactory.Listener debugListener = null; private volatile Boolean debugInitialised = null; private static final String EXTERNAL_JS_SOURCE = "External JavaScript Source/"; private static final String CONFIG_SOURCE_PROPERTY = "openidm.script.javascript.sources"; private static final String CONFIG_DEBUG_PROPERTY = "openidm.script.javascript.debug"; private File externalSourcesFolder = null; private synchronized void initDebugListener() throws ScriptException { if (null == debugInitialised) { // Get here only once when the first factory initialised. String configString = IdentityServer.getInstance().getProperty(CONFIG_DEBUG_PROPERTY, null, String.class); if (null != configString) { String externalSources = IdentityServer.getInstance().getProperty(CONFIG_SOURCE_PROPERTY, null, String.class); if (null != externalSources && externalSources.endsWith(EXTERNAL_JS_SOURCE) && new File(externalSources).isDirectory()) { try { if (null == debugListener) { debugListener = new org.eclipse.wst.jsdt.debug.rhino.debugger.RhinoDebugger( configString); Context.enter().getFactory().addListener(debugListener); Context.exit(); } ((org.eclipse.wst.jsdt.debug.rhino.debugger.RhinoDebugger) debugListener).start(); debugInitialised = Boolean.TRUE; externalSourcesFolder = new File(externalSources).getAbsoluteFile(); } catch (Throwable ex) { //Catch NoClassDefFoundError exception if (!(ex instanceof NoClassDefFoundError)) { //TODO What to do if there is an exception? //throw new ScriptException("Failed to stop RhinoDebugger", ex); logger.error("RhinoDebugger can not be started", ex); } else { //TODO add logging to WARN about the missing RhinoDebugger class logger.warn( "RhinoDebugger can not be started because the JSDT RhinoDebugger and Transport bundles must be deployed."); } } } else { logger.error( "RhinoDebugger can not initialise the source because the {} property must set and has absolute path to '{}' folder.", CONFIG_SOURCE_PROPERTY, EXTERNAL_JS_SOURCE); } debugInitialised = null == debugInitialised ? Boolean.FALSE : debugInitialised; } else if (false /* TODO How to stop */) { try { ((org.eclipse.wst.jsdt.debug.rhino.debugger.RhinoDebugger) debugListener).stop(); } catch (Throwable ex) { //We do not care about the NoClassDefFoundError when we "Stop" if (!(ex instanceof NoClassDefFoundError)) { //TODO What to do if there is an exception? //throw new ScriptException("Failed to stop RhinoDebugger", ex); } } finally { debugInitialised = Boolean.FALSE; } } else { debugInitialised = Boolean.FALSE; } } } @Override public Script newInstance(String name, JsonValue config) throws JsonValueException { String type = config.get("type").asString(); if (type != null && type.equalsIgnoreCase("text/javascript")) { boolean sharedScope = config.get("sharedScope").defaultTo(true).asBoolean(); if (config.isDefined("source")) { try { return initializeScript(name, config.get("source").asString(), sharedScope); } catch (ScriptException se) { // re-cast to show exact value of failure throw new JsonValueException(config.get("source"), se); } } else if (config.isDefined("file")) { // TEMPORARY try { File source = IdentityServer.getFileForProjectPath(config.get("file").asString()); if (!source.isFile()) { source = IdentityServer.getFileForInstallPath(config.get("file").asString()); } return initializeScript(name, source, sharedScope); } catch (ScriptException se) { // re-cast to show exact value of failure throw new JsonValueException(config.get("file"), se); } } else { throw new JsonValueException(config, "expected 'source' or 'file' property"); } } return null; } private Script initializeScript(String name, File source, boolean sharedScope) throws ScriptException { initDebugListener(); if (debugInitialised) { try { FileChannel inChannel = new FileInputStream(source).getChannel(); FileChannel outChannel = new FileOutputStream(getTargetFile(name)).getChannel(); FileLock outLock = outChannel.lock(); FileLock inLock = inChannel.lock(0, inChannel.size(), true); inChannel.transferTo(0, inChannel.size(), outChannel); outLock.release(); inLock.release(); inChannel.close(); outChannel.close(); } catch (IOException e) { logger.warn("JavaScript source was not updated for {}", name, e); } } return new JavaScript(name, source, sharedScope); } private Script initializeScript(String name, String source, boolean sharedScope) throws ScriptException { initDebugListener(); if (debugInitialised) { try { FileChannel outChannel = new FileOutputStream(getTargetFile(name)).getChannel(); FileLock outLock = outChannel.lock(); ByteBuffer buf = ByteBuffer.allocate(source.length()); buf.put(source.getBytes("UTF-8")); buf.flip(); outChannel.write(buf); outLock.release(); outChannel.close(); } catch (IOException e) { logger.warn("JavaScript source was not updated for {}", name, e); } } return new JavaScript(name, source, sharedScope); } private File getTargetFile(String name) { File f = new File(externalSourcesFolder.toURI().resolve(name + JS_EXTENSION)); if (!f.getParentFile().exists()) { f.getParentFile().mkdirs(); } return f; } }