From 5da6e2743761cbdf8f06b3dca9a5cf7c8af1abe3 Mon Sep 17 00:00:00 2001 From: YuriyZ Date: Fri, 13 May 2022 14:51:46 +0300 Subject: [PATCH] feat(jans-core): compile java code on the fly for custom script https://github.com/JanssenProject/jans/issues/318 --- .../external/ExternalUmaRptPolicyService.java | 9 +- jans-bom/pom.xml | 7 + jans-core/script/pom.xml | 5 + .../model/custom/script/CustomScriptType.java | 12 +- .../custom/script/model/CustomScript.java | 29 ++- .../custom/script/model/ScriptError.java | 3 +- .../java/io/jans/service/PythonService.java | 2 +- .../custom/script/CustomScriptManager.java | 189 +++--------------- .../custom/script/ExternalTypeCreator.java | 180 +++++++++++++++++ .../script/test/SimpleJavaCompileTest.java | 24 +++ 10 files changed, 270 insertions(+), 190 deletions(-) create mode 100644 jans-core/script/src/main/java/io/jans/service/custom/script/ExternalTypeCreator.java create mode 100644 jans-core/script/src/test/java/io/jans/service/custom/script/test/SimpleJavaCompileTest.java diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUmaRptPolicyService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUmaRptPolicyService.java index daa5f825b90..48e249554b0 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUmaRptPolicyService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalUmaRptPolicyService.java @@ -14,6 +14,7 @@ import io.jans.service.LookupService; import io.jans.service.custom.script.CustomScriptManager; import io.jans.service.custom.script.ExternalScriptService; +import io.jans.service.custom.script.ExternalTypeCreator; import io.jans.util.StringHelper; import org.apache.commons.io.FileUtils; @@ -46,6 +47,8 @@ public class ExternalUmaRptPolicyService extends ExternalScriptService { private LookupService lookupService; @Inject private CustomScriptManager scriptManager; + @Inject + private ExternalTypeCreator externalTypeCreator; protected Map scriptInumMap; @@ -83,7 +86,7 @@ public CustomScriptConfiguration getScriptByInum(String inum) { } private UmaRptPolicyType policyScript(CustomScriptConfiguration script) { - return HOTSWAP_UMA_SCRIPT ? (UmaRptPolicyType) hotswap(scriptManager, script, true) : + return HOTSWAP_UMA_SCRIPT ? (UmaRptPolicyType) hotswap(externalTypeCreator, script, true) : (UmaRptPolicyType) script.getExternalType(); } @@ -126,7 +129,7 @@ public String getClaimsGatheringScriptName(CustomScriptConfiguration script, Uma } } - public static T hotswap(CustomScriptManager scriptManager, CustomScriptConfiguration script, boolean rptPolicyScript) { + public static T hotswap(ExternalTypeCreator externalTypeCreator, CustomScriptConfiguration script, boolean rptPolicyScript) { if (!HOTSWAP_UMA_SCRIPT) { throw new RuntimeException("UMA script hotswap is not allowed"); } @@ -140,7 +143,7 @@ public static T hotswap(CustomScriptManager scriptManager, CustomScriptConfi try { String scriptCode = FileUtils.readFileToString(new File(scriptPath)); script.getCustomScript().setScript(scriptCode); - return (T) scriptManager.createExternalTypeFromStringWithPythonException(script.getCustomScript(), script.getConfigurationAttributes()); + return (T) externalTypeCreator.createExternalTypeFromStringWithPythonException(script.getCustomScript()); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/jans-bom/pom.xml b/jans-bom/pom.xml index 7e23a164b32..9cd56a00733 100644 --- a/jans-bom/pom.xml +++ b/jans-bom/pom.xml @@ -823,6 +823,13 @@ test + + + net.openhft + compiler + 2.21ea82 + + org.eclipse.jetty diff --git a/jans-core/script/pom.xml b/jans-core/script/pom.xml index 6544d955f35..2d8c3e9e89c 100644 --- a/jans-core/script/pom.xml +++ b/jans-core/script/pom.xml @@ -108,6 +108,11 @@ jans-orm-ldap test + + + net.openhft + compiler + \ No newline at end of file diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java index 8f53b61e600..d5f339e0735 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java @@ -112,10 +112,10 @@ public enum CustomScriptType implements AttributeEnum { private String displayName; private Class customScriptType; private Class customScriptModel; - private String pythonClass; + private String className; private BaseExternalType defaultImplementation; - private static Map MAP_BY_VALUES = new HashMap(); + private static final Map MAP_BY_VALUES = new HashMap<>(); static { for (CustomScriptType enumType : values()) { @@ -124,12 +124,12 @@ public enum CustomScriptType implements AttributeEnum { } CustomScriptType(String value, String displayName, Class customScriptType, - Class customScriptModel, String pythonClass, BaseExternalType defaultImplementation) { + Class customScriptModel, String className, BaseExternalType defaultImplementation) { this.displayName = displayName; this.value = value; this.customScriptType = customScriptType; this.customScriptModel = customScriptModel; - this.pythonClass = pythonClass; + this.className = className; this.defaultImplementation = defaultImplementation; } @@ -153,8 +153,8 @@ public Class getCustomScriptModel() { return customScriptModel; } - public String getPythonClass() { - return pythonClass; + public String getClassName() { + return className; } public BaseExternalType getDefaultImplementation() { diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/model/CustomScript.java b/jans-core/script/src/main/java/io/jans/model/custom/script/model/CustomScript.java index 0379d23d4cc..2ac7d0bebf9 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/model/CustomScript.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/model/CustomScript.java @@ -6,25 +6,24 @@ package io.jans.model.custom.script.model; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import jakarta.persistence.Transient; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; - import io.jans.model.ProgrammingLanguage; import io.jans.model.ScriptLocationType; import io.jans.model.SimpleCustomProperty; import io.jans.model.SimpleExtendedCustomProperty; -import io.jans.util.StringHelper; import io.jans.model.custom.script.CustomScriptType; import io.jans.orm.annotation.AttributeName; import io.jans.orm.annotation.DataEntry; import io.jans.orm.annotation.JsonObject; import io.jans.orm.annotation.ObjectClass; import io.jans.orm.model.base.BaseEntry; +import io.jans.util.StringHelper; +import jakarta.persistence.Transient; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** * Custom script configuration @@ -151,12 +150,10 @@ public void setDescription(String description) { } public String getScript() { - - if(script==null && scriptType==CustomScriptType.PERSON_AUTHENTICATION){ - script=ScriptTemplate.AUTHEN.getValue(); - } - else if(script==null && scriptType!=CustomScriptType.PERSON_AUTHENTICATION){ - script=ScriptTemplate.NO_AUTHEN.getValue(); + if (script == null) { + script = scriptType == CustomScriptType.PERSON_AUTHENTICATION ? + ScriptTemplate.AUTHEN.getValue() : + ScriptTemplate.NO_AUTHEN.getValue(); } return script; } @@ -305,7 +302,7 @@ public void removeModuleProperty(final String modulePropertyName) { } for (Iterator it = modulePropertiesList.iterator(); it.hasNext();) { - SimpleCustomProperty moduleProperty = (SimpleCustomProperty) it.next(); + SimpleCustomProperty moduleProperty = it.next(); if (StringHelper.equalsIgnoreCase(moduleProperty.getValue1(), modulePropertyName)) { it.remove(); break; diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/model/ScriptError.java b/jans-core/script/src/main/java/io/jans/model/custom/script/model/ScriptError.java index 50c38d02b13..3ff80214641 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/model/ScriptError.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/model/ScriptError.java @@ -6,6 +6,7 @@ package io.jans.model.custom.script.model; +import java.io.Serializable; import java.util.Date; /** @@ -13,7 +14,7 @@ * * @author Yuriy Movchan Date: 02/27/2018 */ -public class ScriptError { +public class ScriptError implements Serializable { private Date raisedAt; diff --git a/jans-core/script/src/main/java/io/jans/service/PythonService.java b/jans-core/script/src/main/java/io/jans/service/PythonService.java index d468cc755f4..cc0f94f055e 100644 --- a/jans-core/script/src/main/java/io/jans/service/PythonService.java +++ b/jans-core/script/src/main/java/io/jans/service/PythonService.java @@ -183,7 +183,7 @@ public T loadPythonScript(InputStream scriptFile, String scriptName, String try { currentPythonInterpreter.execfile(scriptFile, scriptName); } catch (Exception ex) { - log.error("Failed to load python file", ex.getMessage(), ex); + log.error("Failed to load python file" + ex.getMessage(), ex); throw new PythonException(String.format("Failed to load python file '%s'", scriptFile), ex); } diff --git a/jans-core/script/src/main/java/io/jans/service/custom/script/CustomScriptManager.java b/jans-core/script/src/main/java/io/jans/service/custom/script/CustomScriptManager.java index a9f62d09385..c6c82050d06 100644 --- a/jans-core/script/src/main/java/io/jans/service/custom/script/CustomScriptManager.java +++ b/jans-core/script/src/main/java/io/jans/service/custom/script/CustomScriptManager.java @@ -6,14 +6,12 @@ package io.jans.service.custom.script; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Date; +import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -31,26 +29,19 @@ import io.jans.service.custom.inject.ReloadScript; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.exception.ExceptionUtils; import io.jans.model.ScriptLocationType; import io.jans.model.SimpleCustomProperty; import io.jans.model.SimpleExtendedCustomProperty; import io.jans.model.custom.script.CustomScriptType; import io.jans.model.custom.script.conf.CustomScriptConfiguration; import io.jans.model.custom.script.model.CustomScript; -import io.jans.model.custom.script.model.ScriptError; import io.jans.model.custom.script.type.BaseExternalType; -import io.jans.service.PythonService; import io.jans.service.cdi.async.Asynchronous; import io.jans.service.cdi.event.Scheduled; import io.jans.service.cdi.event.UpdateScriptEvent; import io.jans.service.timer.event.TimerEvent; import io.jans.service.timer.schedule.TimerSchedule; import io.jans.util.StringHelper; -import org.python.core.PyLong; -import org.python.core.PyObject; import org.slf4j.Logger; /** @@ -59,14 +50,12 @@ * @author Yuriy Movchan Date: 12/03/2014 */ @ApplicationScoped -public class CustomScriptManager implements Serializable { - - private static final long serialVersionUID = -4225890597520443390L; +public class CustomScriptManager { public static final String CUSTOM_SCRIPT_MODIFIED_EVENT_TYPE = "customScriptModifiedEvent"; public static final int DEFAULT_INTERVAL = 30; // 30 seconds - public static final String[] CUSTOM_SCRIPT_CHECK_ATTRIBUTES = { "dn", "inum", "jansRevision", "jansScrTyp", + protected static final String[] CUSTOM_SCRIPT_CHECK_ATTRIBUTES = { "dn", "inum", "jansRevision", "jansScrTyp", "jansModuleProperty", "jansEnabled" }; @Inject @@ -76,7 +65,7 @@ public class CustomScriptManager implements Serializable { private Event timerEvent; @Inject - protected PythonService pythonService; + protected ExternalTypeCreator externalTypeCreator; @Inject protected AbstractCustomScriptService customScriptService; @@ -103,11 +92,10 @@ public void initTimer(List supportedCustomScriptTypes) { configure(); final int delay = 30; - final int interval = DEFAULT_INTERVAL; reload(true); - timerEvent.fire(new TimerEvent(new TimerSchedule(delay, interval), new UpdateScriptEvent(), + timerEvent.fire(new TimerEvent(new TimerSchedule(delay, DEFAULT_INTERVAL), new UpdateScriptEvent(), Scheduled.Literal.INSTANCE)); } @@ -116,6 +104,7 @@ protected void configure() { this.lastFinishedTime = System.currentTimeMillis(); } + @SuppressWarnings("java:S1181") public void reloadTimerEvent(@Observes @Scheduled UpdateScriptEvent updateScriptEvent) { if (this.isActive.get()) { return; @@ -136,6 +125,7 @@ public void reloadTimerEvent(@Observes @Scheduled UpdateScriptEvent updateScript } } + @SuppressWarnings("java:S1172") public void destroy(@BeforeDestroyed(ApplicationScoped.class) ServletContext init) { log.debug("Destroying custom scripts configurations"); if (this.customScriptConfigurations == null) { @@ -173,7 +163,7 @@ private boolean reloadImpl() { // Load current script revisions List customScripts; if (supportedCustomScriptTypes.isEmpty()) { - customScripts = new ArrayList(); + customScripts = new ArrayList<>(); } else { customScripts = customScriptService.findCustomScripts(supportedCustomScriptTypes, CUSTOM_SCRIPT_CHECK_ATTRIBUTES); @@ -215,15 +205,15 @@ private ReloadResult reloadCustomScriptConfigurations( boolean modified = false; if (customScriptConfigurations == null) { - newCustomScriptConfigurations = new HashMap(); + newCustomScriptConfigurations = new HashMap<>(); modified = true; } else { // Clone old map to avoid reload not changed scripts because it's time and CPU // consuming process - newCustomScriptConfigurations = new HashMap(customScriptConfigurations); + newCustomScriptConfigurations = new HashMap<>(customScriptConfigurations); } - List newSupportedCustomScriptInums = new ArrayList(); + List newSupportedCustomScriptInums = new ArrayList<>(); for (CustomScript newCustomScript : newCustomScripts) { if (!newCustomScript.isEnabled()) { continue; @@ -254,12 +244,12 @@ private ReloadResult reloadCustomScriptConfigurations( newCustomScript.getScriptType().getCustomScriptModel(), newCustomScript.getDn()); // Prepare configuration attributes - Map newConfigurationAttributes = new HashMap(); + Map newConfigurationAttributes = new HashMap<>(); List simpleCustomProperties = loadedCustomScript .getConfigurationProperties(); if (simpleCustomProperties == null) { - simpleCustomProperties = new ArrayList(0); + simpleCustomProperties = new ArrayList<>(0); } @@ -283,7 +273,7 @@ private ReloadResult reloadCustomScriptConfigurations( } // Load script - BaseExternalType newCustomScriptExternalType = createExternalType(loadedCustomScript, + BaseExternalType newCustomScriptExternalType = externalTypeCreator.createExternalType(loadedCustomScript, newConfigurationAttributes); CustomScriptConfiguration newCustomScriptConfiguration = new CustomScriptConfiguration( @@ -317,11 +307,13 @@ private ReloadResult reloadCustomScriptConfigurations( private String loadFromFile(String locationPath) { try { - String scriptFromFile = FileUtils.readFileToString(new File(locationPath)); - + String scriptFromFile = FileUtils.readFileToString(new File(locationPath), StandardCharsets.UTF_8); + if (log.isTraceEnabled()) { + log.trace("Loaded from file: {}, script: {}", locationPath, scriptFromFile); + } return scriptFromFile; } catch (IOException ex) { - log.error("Faield to load script from '{}'", locationPath); + log.error("Failed to load script from '{}'", locationPath); } return null; @@ -350,10 +342,10 @@ private boolean destroyCustomScript(CustomScriptConfiguration customScriptConfig private Map> groupCustomScriptConfigurationsByScriptType( Map customScriptConfigurations) { - Map> newCustomScriptConfigurationsByScriptType = new HashMap>(); + Map> newCustomScriptConfigurationsByScriptType = new EnumMap<>(CustomScriptType.class); for (CustomScriptType customScriptType : this.supportedCustomScriptTypes) { - List customConfigurationsByScriptType = new ArrayList(); + List customConfigurationsByScriptType = new ArrayList<>(); newCustomScriptConfigurationsByScriptType.put(customScriptType, customConfigurationsByScriptType); } @@ -369,74 +361,6 @@ private Map> groupCustomScript return newCustomScriptConfigurationsByScriptType; } - private BaseExternalType createExternalType(CustomScript customScript, - Map configurationAttributes) { - String customScriptInum = customScript.getInum(); - - BaseExternalType externalType; - try { - externalType = createExternalTypeFromStringWithPythonException(customScript, configurationAttributes); - } catch (Exception ex) { - log.error("Failed to prepare external type '{}'", ex, customScriptInum); - saveScriptError(customScript, ex, true); - return null; - } - - if (externalType == null) { - log.debug("Using default external type class"); - saveScriptError(customScript, new Exception("Using default external type class"), true); - externalType = customScript.getScriptType().getDefaultImplementation(); - } else { - clearScriptError(customScript); - } - - return externalType; - } - - public BaseExternalType createExternalTypeFromStringWithPythonException(CustomScript customScript, - Map configurationAttributes) throws Exception { - String script = customScript.getScript(); - String scriptName = StringHelper.toLowerCase(customScript.getName()) + ".py"; - if (script == null) { - return null; - } - - CustomScriptType customScriptType = customScript.getScriptType(); - BaseExternalType externalType = null; - - InputStream bis = null; - try { - bis = new ByteArrayInputStream(script.getBytes("UTF-8")); - externalType = pythonService.loadPythonScript(bis, scriptName, customScriptType.getPythonClass(), - customScriptType.getCustomScriptType(), new PyObject[] { new PyLong(System.currentTimeMillis()) }); - } catch (UnsupportedEncodingException e) { - log.error(String.format("%s. Script inum: %s", e.getMessage(), customScript.getInum()), e); - } finally { - IOUtils.closeQuietly(bis); - } - - if (externalType == null) { - return null; - } - - boolean initialized = false; - try { - if (externalType.getApiVersion() > 10) { - initialized = externalType.init(customScript, configurationAttributes); - } else { - initialized = externalType.init(configurationAttributes); - log.warn(" Update the script's init method to init(self, customScript, configurationAttributes)", customScript.getName()); - } - } catch (Exception ex) { - log.error("Failed to initialize custom script: '{}'", ex, customScript.getName()); - } - - if (initialized) { - return externalType; - } - - return null; - } public boolean executeCustomScriptDestroy(CustomScriptConfiguration customScriptConfiguration) { try { @@ -447,73 +371,12 @@ public boolean executeCustomScriptDestroy(CustomScriptConfiguration customScript return externalType.destroy(configurationAttributes); } catch (Exception ex) { log.error(ex.getMessage(), ex); - saveScriptError(customScriptConfiguration.getCustomScript(), ex); + externalTypeCreator.saveScriptError(customScriptConfiguration.getCustomScript(), ex); } return false; } - public void saveScriptError(CustomScript customScript, Exception exception) { - saveScriptError(customScript, exception, false); - } - - public void saveScriptError(CustomScript customScript, Exception exception, boolean overwrite) { - try { - saveScriptErrorImpl(customScript, exception, overwrite); - } catch (Exception ex) { - log.error("Failed to store script '{}' error", customScript.getInum(), ex); - } - } - - protected void saveScriptErrorImpl(CustomScript customScript, Exception exception, boolean overwrite) { - // Load entry from DN - String customScriptDn = customScript.getDn(); - Class scriptType = customScript.getScriptType().getCustomScriptModel(); - CustomScript loadedCustomScript = customScriptService.getCustomScriptByDn(scriptType, customScriptDn); - - // Check if there is error value already - ScriptError currError = loadedCustomScript.getScriptError(); - if (!overwrite && (currError != null)) { - return; - } - - // Save error into script entry - StringBuilder builder = new StringBuilder(); - builder.append(ExceptionUtils.getStackTrace(exception)); - String message = exception.getMessage(); - if (message != null && !StringUtils.isEmpty(message)) { - builder.append("\n==================Further details============================\n"); - builder.append(message); - } - loadedCustomScript.setScriptError(new ScriptError(new Date(), builder.toString())); - customScriptService.update(loadedCustomScript); - } - - public void clearScriptError(CustomScript customScript) { - try { - clearScriptErrorImpl(customScript); - } catch (Exception ex) { - log.error("Failed to clear script '{}' error", customScript.getInum(), ex); - } - } - - protected void clearScriptErrorImpl(CustomScript customScript) { - // Load entry from DN - String customScriptDn = customScript.getDn(); - Class scriptType = customScript.getScriptType().getCustomScriptModel(); - CustomScript loadedCustomScript = customScriptService.getCustomScriptByDn(scriptType, customScriptDn); - - // Check if there is no error - ScriptError currError = loadedCustomScript.getScriptError(); - if (currError == null) { - return; - } - - // Save error into script entry - loadedCustomScript.setScriptError(null); - customScriptService.update(loadedCustomScript); - } - public CustomScriptConfiguration getCustomScriptConfigurationByInum(String inum) { return this.customScriptConfigurations.get(inum); } @@ -523,13 +386,13 @@ public List getCustomScriptConfigurationsByScriptType List tmpCustomScriptConfigurationsByScriptType = this.customScriptConfigurationsByScriptType .get(customScriptType); if (tmpCustomScriptConfigurationsByScriptType == null) { - tmpCustomScriptConfigurationsByScriptType = new ArrayList(0); + tmpCustomScriptConfigurationsByScriptType = new ArrayList<>(0); } - return new ArrayList(tmpCustomScriptConfigurationsByScriptType); + return new ArrayList<>(tmpCustomScriptConfigurationsByScriptType); } public List getCustomScriptConfigurations() { - return new ArrayList(this.customScriptConfigurations.values()); + return new ArrayList<>(this.customScriptConfigurations.values()); } public List getSupportedCustomScriptTypes() { diff --git a/jans-core/script/src/main/java/io/jans/service/custom/script/ExternalTypeCreator.java b/jans-core/script/src/main/java/io/jans/service/custom/script/ExternalTypeCreator.java new file mode 100644 index 00000000000..9a83b00b6a9 --- /dev/null +++ b/jans-core/script/src/main/java/io/jans/service/custom/script/ExternalTypeCreator.java @@ -0,0 +1,180 @@ +package io.jans.service.custom.script; + +import io.jans.exception.PythonException; +import io.jans.model.ProgrammingLanguage; +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.CustomScriptType; +import io.jans.model.custom.script.model.CustomScript; +import io.jans.model.custom.script.model.ScriptError; +import io.jans.model.custom.script.type.BaseExternalType; +import io.jans.service.PythonService; +import io.jans.util.StringHelper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import net.openhft.compiler.CompilerUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.python.core.PyLong; +import org.python.core.PyObject; +import org.slf4j.Logger; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Map; + +/** + * @author Yuriy Zabrovarnyy + */ +@ApplicationScoped +public class ExternalTypeCreator { + + @Inject + protected Logger log; + + @Inject + protected PythonService pythonService; + + @Inject + protected AbstractCustomScriptService customScriptService; + + public BaseExternalType createExternalType(CustomScript customScript, + Map configurationAttributes) { + String customScriptInum = customScript.getInum(); + + BaseExternalType externalType; + try { + if (customScript.getProgrammingLanguage() == ProgrammingLanguage.JAVA) { + externalType = createExternalTypeWithJava(customScript); + } else { + externalType = createExternalTypeFromStringWithPythonException(customScript); + } + } catch (Exception ex) { + log.error("Failed to prepare external type '{}', ex: '{}'", customScriptInum, ex); + saveScriptError(customScript, ex, true); + return null; + } + + externalType = initExternalType(externalType, customScript, configurationAttributes); + + if (externalType == null) { + log.debug("Using default external type class"); + saveScriptError(customScript, new Exception("Using default external type class"), true); + externalType = customScript.getScriptType().getDefaultImplementation(); + } else { + clearScriptError(customScript); + } + + return externalType; + } + + private BaseExternalType initExternalType(BaseExternalType externalType, CustomScript customScript, Map configurationAttributes) { + if (externalType == null) { + return null; + } + + boolean initialized = false; + try { + if (externalType.getApiVersion() > 10) { + initialized = externalType.init(customScript, configurationAttributes); + } else { + initialized = externalType.init(configurationAttributes); + log.warn(" Update the script's init method to init(self, customScript, configurationAttributes), script name: {}", customScript.getName()); + } + } catch (Exception ex) { + log.error("Failed to initialize custom script: '{}', exception: {}", customScript.getName(), ex); + } + + if (initialized) { + return externalType; + } + return null; + } + + private BaseExternalType createExternalTypeWithJava(CustomScript customScript) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + CustomScriptType customScriptType = customScript.getScriptType(); + Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(customScriptType.getClassName(), customScript.getScript()); + return (BaseExternalType) aClass.getDeclaredConstructor().newInstance(); + } + + public BaseExternalType createExternalTypeFromStringWithPythonException(CustomScript customScript) throws PythonException, IOException { + String script = customScript.getScript(); + String scriptName = StringHelper.toLowerCase(customScript.getName()) + ".py"; + if (script == null) { + return null; + } + + CustomScriptType customScriptType = customScript.getScriptType(); + BaseExternalType externalType = null; + + try (InputStream bis = new ByteArrayInputStream(script.getBytes(StandardCharsets.UTF_8))) { + externalType = pythonService.loadPythonScript(bis, scriptName, customScriptType.getClassName(), + customScriptType.getCustomScriptType(), new PyObject[] { new PyLong(System.currentTimeMillis()) }); + } + return externalType; + } + + public void saveScriptError(CustomScript customScript, Exception exception) { + saveScriptError(customScript, exception, false); + } + + public void saveScriptError(CustomScript customScript, Exception exception, boolean overwrite) { + try { + saveScriptErrorImpl(customScript, exception, overwrite); + } catch (Exception ex) { + log.error("Failed to store script '{}' error", customScript.getInum(), ex); + } + } + + protected void saveScriptErrorImpl(CustomScript customScript, Exception exception, boolean overwrite) { + // Load entry from DN + String customScriptDn = customScript.getDn(); + Class scriptType = customScript.getScriptType().getCustomScriptModel(); + CustomScript loadedCustomScript = customScriptService.getCustomScriptByDn(scriptType, customScriptDn); + + // Check if there is error value already + ScriptError currError = loadedCustomScript.getScriptError(); + if (!overwrite && (currError != null)) { + return; + } + + // Save error into script entry + StringBuilder builder = new StringBuilder(); + builder.append(ExceptionUtils.getStackTrace(exception)); + String message = exception.getMessage(); + if (message != null && !StringUtils.isEmpty(message)) { + builder.append("\n==================Further details============================\n"); + builder.append(message); + } + loadedCustomScript.setScriptError(new ScriptError(new Date(), builder.toString())); + customScriptService.update(loadedCustomScript); + } + + public void clearScriptError(CustomScript customScript) { + try { + clearScriptErrorImpl(customScript); + } catch (Exception ex) { + log.error("Failed to clear script '{}' error", customScript.getInum(), ex); + } + } + + protected void clearScriptErrorImpl(CustomScript customScript) { + // Load entry from DN + String customScriptDn = customScript.getDn(); + Class scriptType = customScript.getScriptType().getCustomScriptModel(); + CustomScript loadedCustomScript = customScriptService.getCustomScriptByDn(scriptType, customScriptDn); + + // Check if there is no error + ScriptError currError = loadedCustomScript.getScriptError(); + if (currError == null) { + return; + } + + // Save error into script entry + loadedCustomScript.setScriptError(null); + customScriptService.update(loadedCustomScript); + } +} diff --git a/jans-core/script/src/test/java/io/jans/service/custom/script/test/SimpleJavaCompileTest.java b/jans-core/script/src/test/java/io/jans/service/custom/script/test/SimpleJavaCompileTest.java new file mode 100644 index 00000000000..66c45880497 --- /dev/null +++ b/jans-core/script/src/test/java/io/jans/service/custom/script/test/SimpleJavaCompileTest.java @@ -0,0 +1,24 @@ +package io.jans.service.custom.script.test; + +import net.openhft.compiler.CompilerUtils; + +import java.lang.reflect.InvocationTargetException; + +/** + * @author Yuriy Zabrovarnyy + */ +public class SimpleJavaCompileTest { + + public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + String className = "MyClass"; + String javaCode = + "public class MyClass implements Runnable {\n" + + " public void run() {\n" + + " System.out.println(\"Hello World\");\n" + + " }\n" + + "}\n"; + Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode); + Runnable runner = (Runnable) aClass.getDeclaredConstructor().newInstance(); + runner.run(); + } +}