Java tutorial
/*************************************************************** * This file is part of the [fleXive](R) framework. * * Copyright (c) 1999-2014 * UCS - unique computing solutions gmbh (http://www.ucs.at) * All rights reserved * * The [fleXive](R) project is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public * License version 2.1 or higher as published by the Free Software Foundation. * * The GNU Lesser General Public License can be found at * http://www.gnu.org/licenses/lgpl.html. * A copy is found in the textfile LGPL.txt and important notices to the * license from the author are found in LICENSE.txt distributed with * these libraries. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For further information about UCS - unique computing solutions gmbh, * please see the company website: http://www.ucs.at * * For further information about [fleXive](R), please see the * project website: http://www.flexive.org * * * This copyright notice MUST APPEAR in all copies of the file! ***************************************************************/ package com.flexive.ejb.beans; import com.flexive.core.Database; import com.flexive.core.JDK6Scripting; import com.flexive.core.security.UserTicketImpl; import com.flexive.core.storage.StorageManager; import com.flexive.core.structure.FxEnvironmentImpl; import com.flexive.core.structure.StructureLoader; import com.flexive.shared.*; import com.flexive.shared.configuration.Parameter; import com.flexive.shared.configuration.SystemParameters; import com.flexive.shared.content.FxPermissionUtils; import com.flexive.shared.exceptions.*; import com.flexive.shared.interfaces.ScriptingEngine; import com.flexive.shared.interfaces.ScriptingEngineLocal; import com.flexive.shared.interfaces.SequencerEngineLocal; import com.flexive.shared.scripting.*; import com.flexive.shared.security.Role; import com.flexive.shared.security.UserTicket; import com.flexive.shared.structure.FxAssignment; import com.flexive.shared.structure.FxType; import com.google.common.collect.Lists; import groovy.lang.Binding; import groovy.lang.GroovyShell; import groovy.lang.Script; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.groovy.control.CompilationFailedException; import javax.annotation.Resource; import javax.ejb.*; import java.io.File; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import static com.flexive.core.DatabaseConst.*; import static com.flexive.shared.EJBLookup.getDivisionConfigurationEngine; /** * ScriptingEngine implementation * * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at) */ @Stateless(name = "ScriptingEngine", mappedName = "ScriptingEngine") @TransactionManagement(TransactionManagementType.CONTAINER) public class ScriptingEngineBean implements ScriptingEngine, ScriptingEngineLocal { // Our logger private static final Log LOG = LogFactory.getLog(ScriptingEngineBean.class); @Resource javax.ejb.SessionContext ctx; @EJB SequencerEngineLocal seq; /** * Static helper class for deployment specific caches and settings */ static class LocalScriptingCache { /** * Cache for compile groovy scripts */ static ConcurrentMap<Long, Script> groovyScriptCache = new ConcurrentHashMap<Long, Script>(50); /** * Timestamp of the script cache */ static volatile long scriptCacheTimestamp = -1; /** * Scripts by Event cache */ static volatile Map<FxScriptEvent, List<Long>> scriptsByEvent = new HashMap<FxScriptEvent, List<Long>>(10); static volatile List<FxScriptRunInfo> runOnceInfos = null; static Script checkoutScript(long scriptId, FxScriptInfo si, String code) throws FxInvalidParameterException { Script script = LocalScriptingCache.groovyScriptCache.get(scriptId); if (script == null) { try { GroovyShell shell = new GroovyShell(); script = shell.parse(code); } catch (CompilationFailedException e) { throw new FxInvalidParameterException(si.getName(), "ex.general.scripting.compileFailed", si.getName(), e.getMessage()); } catch (Throwable t) { throw new FxInvalidParameterException(si.getName(), "ex.general.scripting.exception", si.getName(), t.getMessage()); } if (si.isCached()) LocalScriptingCache.groovyScriptCache.putIfAbsent(scriptId, script); } return null; } static void checkinScript(long scriptId, Script script) { } /** * Execute a script. * This method does not check the calling user's role nor does it cache scripts. * It should only be used to execute code from the groovy console or code that is not to be expected to * be run more than once. * * @param name name of the script, extension is needed to choose interpreter * @param binding bindings to apply * @param code the script code * @return last script evaluation result * @throws FxApplicationException on errors */ @SuppressWarnings({ "ThrowableInstanceNeverThrown" }) static FxScriptResult internal_runScript(String name, FxScriptBinding binding, String code) throws FxApplicationException { if (name == null) name = "unknown"; if (name.indexOf('.') < 0) throw new FxInvalidParameterException(name, "ex.general.scripting.noExtension", name); if (!FxSharedUtils.isGroovyScript(name)) { try { return JDK6Scripting.runScript(name, binding, code); } catch (Throwable t) { if (name.endsWith(".class")) { // probable cause: compiled script files (or stubs) ended up in the scripts folder LOG.warn(".class file is not a script: " + name); return null; } LOG.error("Exception running script [" + name + "]: " + t.getMessage(), t); if (t instanceof FxApplicationException) throw (FxApplicationException) t; if (t instanceof java.lang.reflect.InvocationTargetException && t.getCause() != null) { if (t.getCause() instanceof FxApplicationException) throw (FxApplicationException) t.getCause(); throw new FxInvalidParameterException(name, t.getCause(), "ex.general.scripting.exception", name, t.getCause().getMessage()).asRuntimeException(); } throw new FxInvalidParameterException(name, t, "ex.general.scripting.exception", name, t.getMessage()).asRuntimeException(); } } if (binding != null) { if (!binding.getProperties().containsKey("ticket")) binding.setVariable("ticket", FxContext.getUserTicket()); if (!binding.getProperties().containsKey("environment")) binding.setVariable("environment", CacheAdmin.getEnvironment()); binding.setVariable("scriptname", name); } if (FxSharedUtils.isGroovyScript(name)) { //we prefer the native groovy binding try { GroovyShell shell = new GroovyShell(); Script script = shell.parse(code); if (binding != null) script.setBinding(new Binding(binding.getProperties())); Object result = script.run(); return new FxScriptResult(binding, result); } catch (Throwable e) { LOG.error("Exception running script [" + name + "]: " + e.getMessage(), e); if (e instanceof FxApplicationException) throw (FxApplicationException) e; throw new GenericScriptException(name, LOG, e, "ex.general.scripting.exception", name, e.getMessage()); } } throw new FxInvalidParameterException(name, "ex.general.scripting.needJDK6", name).asRuntimeException(); } } private static final Object RUNONCE_LOCK = new Object(); /** * Marker exception that wraps a script exception (used for more accurate error reporting). */ public static class GenericScriptException extends FxInvalidParameterException { private static final long serialVersionUID = 6437523270242591223L; private GenericScriptException(String parameter, Log log, Throwable cause, String key, Object... values) { super(parameter, log, cause, key, values); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public String loadScriptCode(long scriptId) throws FxApplicationException { Connection con = null; PreparedStatement ps = null; String sql; String code; try { // Obtain a database connection con = Database.getDbConnection(); // 1 sql = "SELECT SDATA FROM " + TBL_SCRIPTS + " WHERE ID=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ResultSet rs = ps.executeQuery(); if (rs == null || !rs.next()) throw new FxNotFoundException("ex.scripting.notFound.id", scriptId); code = rs.getString(1); } catch (SQLException exc) { EJBUtils.rollback(ctx); throw new FxLoadException(LOG, exc, "ex.scripting.load.failed", scriptId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); } return code; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List<FxScriptInfo> getScriptInfos() throws FxApplicationException { Connection con = null; PreparedStatement ps = null; String sql; ArrayList<FxScriptInfo> slist = new ArrayList<FxScriptInfo>(); try { // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 5 6 sql = "SELECT ID, SNAME,SDESC,STYPE,ACTIVE, IS_CACHED FROM " + TBL_SCRIPTS + " ORDER BY ID"; ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while (rs != null && rs.next()) { slist.add(new FxScriptInfo(rs.getInt(1), FxScriptEvent.getById(rs.getLong(4)), rs.getString(2), rs.getString(3), rs.getBoolean(5), rs.getBoolean(6))); } } catch (SQLException exc) { EJBUtils.rollback(ctx); throw new FxLoadException(LOG, exc, "ex.scripts.load.failed", exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); } return slist; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void updateScriptInfo(FxScriptInfoEdit script) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; try { String code = ""; if (script.getCode() != null) code = script.getCode(); // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 5 6 7 sql = "UPDATE " + TBL_SCRIPTS + " SET SNAME=?,SDESC=?,SDATA=?,STYPE=?,ACTIVE=?,IS_CACHED=? WHERE ID=?"; ps = con.prepareStatement(sql); ps.setString(1, script.getName()); ps.setString(2, script.getDescription()); StorageManager.setBigString(ps, 3, code); ps.setLong(4, script.getEvent().getId()); ps.setBoolean(5, script.isActive()); ps.setBoolean(6, script.isCached()); ps.setLong(7, script.getId()); ps.executeUpdate(); // remove script from cache if necessary if (FxSharedUtils.isGroovyScript(script.getName()) && !script.isCached()) LocalScriptingCache.groovyScriptCache.remove(script.getId()); success = true; } catch (SQLException exc) { throw new FxUpdateException(LOG, exc, "ex.scripting.update.failed", script.getName(), exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void updateScriptCode(long scriptId, String code) throws FxApplicationException { FxScriptInfo si = CacheAdmin.getEnvironment().getScript(scriptId); updateScriptInfo(new FxScriptInfoEdit(si.getId(), si.getEvent(), si.getName(), si.getDescription(), code, si.isActive(), si.isCached())); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List<Long> getByScriptEvent(FxScriptEvent scriptEvent) { long timeStamp = CacheAdmin.getEnvironment().getTimeStamp(); if (timeStamp != LocalScriptingCache.scriptCacheTimestamp) resetLocalCaches(timeStamp); List<Long> cached = LocalScriptingCache.scriptsByEvent.get(scriptEvent); if (cached != null) return cached; Connection con = null; PreparedStatement ps = null; String sql; List<Long> ret = new ArrayList<Long>(10); try { // Obtain a database connection con = Database.getDbConnection(); // 1 sql = "SELECT DISTINCT ID FROM " + TBL_SCRIPTS + " WHERE STYPE=? ORDER BY ID"; ps = con.prepareStatement(sql); ps.setLong(1, scriptEvent.getId()); ResultSet rs = ps.executeQuery(); while (rs != null && rs.next()) ret.add(rs.getLong(1)); } catch (SQLException exc) { EJBUtils.rollback(ctx); throw new FxDbException(LOG, exc, "ex.db.sqlError", exc.getMessage()).asRuntimeException(); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); } LocalScriptingCache.scriptsByEvent.put(scriptEvent, Collections.unmodifiableList(ret)); return ret; } /** * Reset all local caches in use * * @param timeStamp new timestamp to use for comparing the script cache */ private void resetLocalCaches(long timeStamp) { LocalScriptingCache.scriptCacheTimestamp = timeStamp; LocalScriptingCache.groovyScriptCache.clear(); LocalScriptingCache.scriptsByEvent.clear(); } /** * {@inheritDoc} */ @Override @SuppressWarnings({ "unchecked" }) @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List<FxScriptRunInfo> getRunOnceInformation() throws FxApplicationException { if (LocalScriptingCache.runOnceInfos != null) { return Lists.newArrayList(LocalScriptingCache.runOnceInfos); } else { return getDivisionConfigurationEngine().get(SystemParameters.DIVISION_RUNONCE_INFOS); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptInfo createScript(FxScriptInfoEdit scriptInfo) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxScriptInfo si; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; try { // for groovy scripts set cached flag to true si = new FxScriptInfo(seq.getId(FxSystemSequencer.SCRIPTS), scriptInfo.getEvent(), scriptInfo.getName(), scriptInfo.getDescription(), scriptInfo.isActive(), scriptInfo.isCached()); String code = scriptInfo.getCode() == null ? "" : scriptInfo.getCode(); // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 5 6 7 sql = "INSERT INTO " + TBL_SCRIPTS + " (ID,SNAME,SDESC,SDATA,STYPE,ACTIVE,IS_CACHED) VALUES (?,?,?,?,?,?,?)"; ps = con.prepareStatement(sql); ps.setLong(1, si.getId()); ps.setString(2, si.getName()); ps.setString(3, si.getDescription()); StorageManager.setBigString(ps, 4, code); ps.setLong(5, si.getEvent().getId()); ps.setBoolean(6, si.isActive()); ps.setBoolean(7, si.isCached()); ps.executeUpdate(); success = true; } catch (SQLException exc) { if (StorageManager.isUniqueConstraintViolation(exc)) throw new FxEntryExistsException("ex.scripting.name.notUnique", scriptInfo.getName()); throw new FxCreateException(LOG, exc, "ex.scripting.create.failed", scriptInfo.getName(), exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return si; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptInfo createScriptFromLibrary(String libraryname, FxScriptInfo scriptInfo) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); String code = FxSharedUtils.loadFromInputStream( FxSharedUtils.getResourceStream("fxresources/scripts/library/" + libraryname), -1); if (code == null || code.length() == 0) throw new FxNotFoundException("ex.scripting.load.library.failed", libraryname); return createScript(new FxScriptInfoEdit(-1, scriptInfo.getEvent(), scriptInfo.getName(), scriptInfo.getDescription(), code, scriptInfo.isActive(), scriptInfo.isCached())); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptInfo createScriptFromDropLibrary(String dropName, String libraryname, FxScriptInfo scriptInfo) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); String code = FxSharedUtils.loadFromInputStream( FxSharedUtils.getResourceStream(dropName + "Resources/scripts/library/" + libraryname), -1); if (code == null || code.length() == 0) { // this might be a jar file if (LOG.isDebugEnabled()) { LOG.debug( "Failed to locate script in regular application library, checking for drop application in classpath JARs"); } try { final FxDropApplication dropApplication = FxSharedUtils.getDropApplication(dropName); final Map<String, String> libraries = dropApplication.loadTextResources("scripts/library/"); for (Map.Entry<String, String> libEntry : libraries.entrySet()) { if (libEntry.getKey().endsWith('/' + libraryname) || libEntry.getKey().endsWith(File.separator + libraryname)) { code = libEntry.getValue(); break; } } } catch (Exception e) { LOG.error("Failed to load library scripts for " + dropName + " from JAR file: " + e.getMessage(), e); } } if (code == null || code.length() == 0) { throw new FxNotFoundException("ex.scripting.load.library.failed", libraryname); } return createScript(new FxScriptInfoEdit(-1, scriptInfo.getEvent(), scriptInfo.getName(), scriptInfo.getDescription(), code, scriptInfo.isActive(), scriptInfo.isCached())); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void remove(long scriptId) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; final List<FxScriptSchedule> schedules = CacheAdmin.getEnvironment().getScriptSchedulesForScript(scriptId); try { // Obtain a database connection con = Database.getDbConnection(); sql = "DELETE FROM " + TBL_SCRIPT_MAPPING_ASSIGN + " WHERE SCRIPT=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.executeUpdate(); ps.close(); sql = "DELETE FROM " + TBL_SCRIPT_MAPPING_TYPES + " WHERE SCRIPT=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.executeUpdate(); ps.close(); sql = "DELETE FROM " + TBL_SCRIPT_SCHEDULES + " WHERE SCRIPT=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.executeUpdate(); ps.close(); // 1 sql = "DELETE FROM " + TBL_SCRIPTS + " WHERE ID=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.executeUpdate(); // remove script schedules from scheduler if (EJBLookup.getTimerService().isInstalled()) { for (FxScriptSchedule ss : schedules) { try { boolean deleteSuccess = EJBLookup.getTimerService().deleteScriptSchedule(ss); if (deleteSuccess) LOG.debug("Script schedule with id " + ss.getId() + " successfully deleted from scheduler"); else LOG.warn("Failed to delete script schedule with id " + ss.getId() + " from scheduler"); } catch (Exception e) { LOG.error("Error removing script schedule from scheduler", e); } } } success = true; } catch (SQLException exc) { throw new FxRemoveException(LOG, exc, "ex.scripting.remove.failed", scriptId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public FxScriptResult runScript(String scriptName, FxScriptBinding binding) throws FxApplicationException { return runScript(CacheAdmin.getEnvironment().getScript(scriptName).getId(), binding); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public FxScriptResult runScript(long scriptId, FxScriptBinding binding) throws FxApplicationException { FxScriptInfo si = CacheAdmin.getEnvironment().getScript(scriptId); if (!si.isActive()) { if (LOG.isDebugEnabled()) { LOG.debug( "Script [" + si.getName() + "], Id " + si.getId() + " is deactivated and will not be run!"); } return new FxScriptResult(binding, null); } if (!FxSharedUtils.isGroovyScript(si.getName())) return LocalScriptingCache.internal_runScript(si.getName(), binding, loadScriptCode(si.getId())); if (si.getEvent() == FxScriptEvent.Manual) FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptExecution); long timeStamp = CacheAdmin.getEnvironment().getTimeStamp(); if (timeStamp != LocalScriptingCache.scriptCacheTimestamp) resetLocalCaches(timeStamp); Script script = LocalScriptingCache.groovyScriptCache.get(scriptId); if (script == null) { try { GroovyShell shell = new GroovyShell(); script = shell.parse(loadScriptCode(scriptId)); } catch (CompilationFailedException e) { throw new FxInvalidParameterException(si.getName(), LOG, "ex.general.scripting.compileFailed", si.getName(), e.getMessage()); } catch (Throwable t) { throw new FxInvalidParameterException(si.getName(), "ex.general.scripting.exception", si.getName(), t.getMessage()); } if (si.isCached()) { Script cachedScript = LocalScriptingCache.groovyScriptCache.putIfAbsent(scriptId, script); if (cachedScript != null) { script = cachedScript; } } } if (binding == null) binding = new FxScriptBinding(); if (!binding.getProperties().containsKey("ticket")) binding.setVariable("ticket", FxContext.getUserTicket()); if (!binding.getProperties().containsKey("environment")) binding.setVariable("environment", CacheAdmin.getEnvironment()); binding.setVariable("scriptname", si.getName()); synchronized (script) { try { Object result; script.setBinding(new Binding(binding.getProperties())); result = script.run(); return new FxScriptResult(new FxScriptBinding(binding.getProperties()), result); } catch (Throwable e) { if (e instanceof FxApplicationException) throw (FxApplicationException) e; LOG.error("Scripting error: " + e.getMessage(), e); throw new FxInvalidParameterException(si.getName(), "ex.general.scripting.exception", si.getName(), e.getMessage()); } finally { // don't leave binding in script cache script.setBinding(null); } } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public FxScriptResult runScript(long scriptId) throws FxApplicationException { return runScript(scriptId, null); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptMappingEntry createAssignmentScriptMapping(FxScriptEvent scriptEvent, long scriptId, long assignmentId, boolean active, boolean derivedUsage) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxScriptMappingEntry sm; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; //check existance CacheAdmin.getEnvironment().getScript(scriptId); checkAssignmentScriptConsistency(scriptId, assignmentId, scriptEvent, active, derivedUsage); try { long[] derived; if (!derivedUsage) derived = new long[0]; else { List<FxAssignment> ass = CacheAdmin.getEnvironment().getDerivedAssignments(assignmentId); derived = new long[ass.size()]; for (int i = 0; i < ass.size(); i++) derived[i] = ass.get(i).getId(); } sm = new FxScriptMappingEntry(scriptEvent, scriptId, active, derivedUsage, assignmentId, derived); // Obtain a database connection con = Database.getDbConnection(); sql = "INSERT INTO " + TBL_SCRIPT_MAPPING_ASSIGN + " (ASSIGNMENT,SCRIPT,DERIVED_USAGE,ACTIVE,STYPE) VALUES " + //1,2,3,4,5 "(?,?,?,?,?)"; ps = con.prepareStatement(sql); ps.setLong(1, sm.getId()); ps.setLong(2, sm.getScriptId()); ps.setBoolean(3, sm.isDerivedUsage()); ps.setBoolean(4, sm.isActive()); ps.setLong(5, sm.getScriptEvent().getId()); ps.executeUpdate(); success = true; } catch (SQLException exc) { if (StorageManager.isUniqueConstraintViolation(exc)) throw new FxEntryExistsException("ex.scripting.mapping.assign.notUnique", scriptId, assignmentId); throw new FxCreateException(LOG, exc, "ex.scripting.mapping.assign.create.failed", scriptId, assignmentId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return sm; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptMappingEntry createAssignmentScriptMapping(long scriptId, long typeId, boolean active, boolean derivedUsage) throws FxApplicationException { FxScriptInfo si = CacheAdmin.getEnvironment().getScript(scriptId); return createAssignmentScriptMapping(si.getEvent(), scriptId, typeId, active, derivedUsage); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptMappingEntry createTypeScriptMapping(FxScriptEvent scriptEvent, long scriptId, long typeId, boolean active, boolean derivedUsage) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxScriptMappingEntry sm; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; //check existance CacheAdmin.getEnvironment().getScript(scriptId); //check consistency checkTypeScriptConsistency(scriptId, typeId, scriptEvent, active, derivedUsage); try { long[] derived; if (!derivedUsage) derived = new long[0]; else { List<FxType> types = CacheAdmin.getEnvironment().getType(typeId).getDerivedTypes(); derived = new long[types.size()]; for (int i = 0; i < types.size(); i++) derived[i] = types.get(i).getId(); } sm = new FxScriptMappingEntry(scriptEvent, scriptId, active, derivedUsage, typeId, derived); // Obtain a database connection con = Database.getDbConnection(); sql = "INSERT INTO " + TBL_SCRIPT_MAPPING_TYPES + " (TYPEDEF,SCRIPT,DERIVED_USAGE,ACTIVE,STYPE) VALUES " + //1,2,3,4,5 "(?,?,?,?,?)"; ps = con.prepareStatement(sql); ps.setLong(1, sm.getId()); ps.setLong(2, sm.getScriptId()); ps.setBoolean(3, sm.isDerivedUsage()); ps.setBoolean(4, sm.isActive()); ps.setLong(5, sm.getScriptEvent().getId()); ps.executeUpdate(); success = true; } catch (SQLException exc) { if (StorageManager.isUniqueConstraintViolation(exc)) throw new FxEntryExistsException("ex.scripting.mapping.type.notUnique", scriptId, typeId); throw new FxCreateException(LOG, exc, "ex.scripting.mapping.type.create.failed", scriptId, typeId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return sm; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptMappingEntry createTypeScriptMapping(long scriptId, long typeId, boolean active, boolean derivedUsage) throws FxApplicationException { FxScriptInfo si = CacheAdmin.getEnvironment().getScript(scriptId); return createTypeScriptMapping(si.getEvent(), scriptId, typeId, active, derivedUsage); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeAssignmentScriptMapping(long scriptId, long assignmentId) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // 1 2 sql = "DELETE FROM " + TBL_SCRIPT_MAPPING_ASSIGN + " WHERE SCRIPT=? AND ASSIGNMENT=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.setLong(2, assignmentId); ps.executeUpdate(); success = true; } catch (SQLException exc) { throw new FxRemoveException(LOG, exc, "ex.scripting.mapping.assign.remove.failed", scriptId, assignmentId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeAssignmentScriptMappingForEvent(long scriptId, long assignmentId, FxScriptEvent event) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // 1 2 sql = "DELETE FROM " + TBL_SCRIPT_MAPPING_ASSIGN + " WHERE SCRIPT=? AND ASSIGNMENT=? AND STYPE=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.setLong(2, assignmentId); ps.setLong(3, event.getId()); ps.executeUpdate(); success = true; } catch (SQLException exc) { throw new FxRemoveException(LOG, exc, "ex.scripting.mapping.assign.remove.failed", scriptId, assignmentId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeTypeScriptMapping(long scriptId, long typeId) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // 1 2 sql = "DELETE FROM " + TBL_SCRIPT_MAPPING_TYPES + " WHERE SCRIPT=? AND TYPEDEF=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.setLong(2, typeId); ps.executeUpdate(); success = true; } catch (SQLException exc) { throw new FxRemoveException(LOG, exc, "ex.scripting.mapping.type.remove.failed", scriptId, typeId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void removeTypeScriptMappingForEvent(long scriptId, long typeId, FxScriptEvent event) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; try { // Obtain a database connection con = Database.getDbConnection(); // 1 2 sql = "DELETE FROM " + TBL_SCRIPT_MAPPING_TYPES + " WHERE SCRIPT=? AND TYPEDEF=? AND STYPE=?"; ps = con.prepareStatement(sql); ps.setLong(1, scriptId); ps.setLong(2, typeId); ps.setLong(3, event.getId()); ps.executeUpdate(); success = true; } catch (SQLException exc) { throw new FxRemoveException(LOG, exc, "ex.scripting.mapping.type.remove.failed", scriptId, typeId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptMappingEntry updateAssignmentScriptMappingForEvent(long scriptId, long assignmentId, FxScriptEvent event, boolean active, boolean derivedUsage) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxScriptMappingEntry sm; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; //check script existance CacheAdmin.getEnvironment().getScript(scriptId); checkAssignmentScriptConsistency(scriptId, assignmentId, event, active, derivedUsage); try { long[] derived; if (!derivedUsage) derived = new long[0]; else { List<FxAssignment> ass = CacheAdmin.getEnvironment().getDerivedAssignments(assignmentId); derived = new long[ass.size()]; for (int i = 0; i < ass.size(); i++) derived[i] = ass.get(i).getId(); } sm = new FxScriptMappingEntry(event, scriptId, active, derivedUsage, assignmentId, derived); // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 sql = "UPDATE " + TBL_SCRIPT_MAPPING_ASSIGN + " SET DERIVED_USAGE=?,ACTIVE=? WHERE ASSIGNMENT=? AND SCRIPT=? AND STYPE=?"; ps = con.prepareStatement(sql); ps.setBoolean(1, sm.isDerivedUsage()); ps.setBoolean(2, sm.isActive()); ps.setLong(3, sm.getId()); ps.setLong(4, sm.getScriptId()); ps.setLong(5, sm.getScriptEvent().getId()); ps.executeUpdate(); success = true; } catch (SQLException exc) { if (StorageManager.isUniqueConstraintViolation(exc)) throw new FxEntryExistsException("ex.scripting.mapping.assign.notUnique", scriptId, assignmentId); throw new FxUpdateException(LOG, exc, "ex.scripting.mapping.assign.update.failed", scriptId, assignmentId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return sm; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public FxScriptMappingEntry updateTypeScriptMappingForEvent(long scriptId, long typeId, FxScriptEvent event, boolean active, boolean derivedUsage) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxScriptMappingEntry sm; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; //check existance CacheAdmin.getEnvironment().getScript(scriptId); //check consistency checkTypeScriptConsistency(scriptId, typeId, event, active, derivedUsage); try { long[] derived; if (!derivedUsage) derived = new long[0]; else { List<FxType> types = CacheAdmin.getEnvironment().getType(typeId).getDerivedTypes(); derived = new long[types.size()]; for (int i = 0; i < types.size(); i++) derived[i] = types.get(i).getId(); } sm = new FxScriptMappingEntry(event, scriptId, active, derivedUsage, typeId, derived); // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 5 sql = "UPDATE " + TBL_SCRIPT_MAPPING_TYPES + " SET DERIVED_USAGE=?,ACTIVE=? WHERE TYPEDEF=? AND SCRIPT=? AND STYPE=?"; ps = con.prepareStatement(sql); ps.setBoolean(1, sm.isDerivedUsage()); ps.setBoolean(2, sm.isActive()); ps.setLong(3, sm.getId()); ps.setLong(4, sm.getScriptId()); ps.setLong(5, sm.getScriptEvent().getId()); ps.executeUpdate(); success = true; } catch (SQLException exc) { if (StorageManager.isUniqueConstraintViolation(exc)) throw new FxEntryExistsException("ex.scripting.mapping.type.notUnique", scriptId, typeId); throw new FxUpdateException(LOG, exc, "ex.scripting.mapping.type.update.failed", scriptId, typeId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return sm; } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void executeRunOnceScripts() throws FxApplicationException { final long start = System.currentTimeMillis(); runOnce(SystemParameters.DIVISION_RUNONCE, "flexive", "fxresources", "[fleXive]"); if (LOG.isInfoEnabled()) { LOG.info("Executed flexive run-once scripts in " + (System.currentTimeMillis() - start) + "ms"); } } /** * Execute all runOnce scripts in the resource denoted by prefix if param is "false" * * @param param boolean parameter that will be flagged as "true" once the scripts are run * @param dropName the drop application name * @param prefix resource directory prefix * @param applicationName the corresponding application name (for debug messages) * @throws FxApplicationException on errors */ private void runOnce(Parameter<Boolean> param, String dropName, String prefix, String applicationName) throws FxApplicationException { synchronized (RUNONCE_LOCK) { try { Boolean executed = getDivisionConfigurationEngine().get(param); if (executed) { return; } } catch (FxApplicationException e) { LOG.error(e); return; } //noinspection unchecked final ArrayList<FxScriptRunInfo> divisionRunOnceInfos = Lists .newArrayList(getDivisionConfigurationEngine().get(SystemParameters.DIVISION_RUNONCE_INFOS)); LocalScriptingCache.runOnceInfos = new CopyOnWriteArrayList<FxScriptRunInfo>(); LocalScriptingCache.runOnceInfos.addAll(divisionRunOnceInfos); FxContext.get().setExecutingRunOnceScripts(true); try { executeInitializerScripts("runonce", dropName, prefix, applicationName, new RunonceScriptExecutor(applicationName)); } finally { FxContext.get().setExecutingRunOnceScripts(false); } // TODO: this fails to update the script infos when the transaction was rolled back because of a script error FxContext.get().runAsSystem(); try { divisionRunOnceInfos.clear(); divisionRunOnceInfos.addAll(LocalScriptingCache.runOnceInfos); getDivisionConfigurationEngine().put(SystemParameters.DIVISION_RUNONCE_INFOS, divisionRunOnceInfos); getDivisionConfigurationEngine().put(param, true); LocalScriptingCache.runOnceInfos = null; } finally { FxContext.get().stopRunAsSystem(); } } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void executeDropRunOnceScripts(Parameter<Boolean> param, String dropName) throws FxApplicationException { if (!FxSharedUtils.getDrops().contains(dropName)) throw new FxInvalidParameterException("dropName", "ex.scripting.drop.notFound", dropName); runOnce(param, dropName, dropName + "Resources", dropName); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void executeStartupScripts() { executeInitializerScripts("startup", "flexive", "fxresources", "[fleXive]", new NonFailingScriptExecutor()); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void executeDropStartupScripts(String dropName) throws FxApplicationException { if (!FxSharedUtils.getDrops().contains(dropName)) throw new FxInvalidParameterException("dropName", "ex.scripting.drop.notFound", dropName); executeInitializerScripts("startup", dropName, dropName + "Resources", "drop " + dropName, new NonFailingScriptExecutor()); } private void executeInitializerScripts(String folder, String dropName, String prefix, String applicationName, ScriptExecutor scriptExecutor) { if (LOG.isInfoEnabled()) { LOG.info("Executing " + folder + " scripts for " + applicationName); } final InputStream scriptIndex = FxSharedUtils .getResourceStream(prefix + "/scripts/" + folder + "/scriptindex.flexive"); if (scriptIndex != null) { // load cached file list from script index file for (String entry : FxSharedUtils.loadFromInputStream(scriptIndex, -1).replaceAll("\r", "") .split("\n")) { final String[] values = entry.split("\\|"); // name, size if (StringUtils.isNotBlank(values[0])) { scriptExecutor.runScript(values[0], FxSharedUtils.loadFromInputStream( FxSharedUtils.getResourceStream(prefix + "/scripts/" + folder + "/" + values[0]))); } } } else { // scan classpath from shared resource JAR try { final FxDropApplication dropApplication = FxSharedUtils.getDropApplication(dropName); final Map<String, String> resources = dropApplication.loadTextResources("scripts/" + folder + "/"); if (resources.isEmpty()) { if (LOG.isInfoEnabled()) { LOG.info("Found no scripts in drop " + dropName + " in path \"scripts/" + folder + "/\""); } } else { // sort by name final List<String> names = Lists.newArrayList(resources.keySet()); Collections.sort(names); // execute scripts for (String name : names) { scriptExecutor.runScript( // extract filename name.substring( Math.max(name.lastIndexOf('/'), name.lastIndexOf(File.separator)) + 1), // load script code resources.get(name)); } } } catch (Exception e) { LOG.error("Failed to load " + folder + " scripts for " + dropName + " from JAR file: " + e.getMessage(), e); } } } /** * Helper interface to provide customized "script executors" (startup, runonce scripts). */ private interface ScriptExecutor { /** * Run the given script. * * @param name the script filename (extension determines language) * @param code the code to be executed * @return true if script evaluation should continue */ boolean runScript(String name, String code); } private class NonFailingScriptExecutor implements ScriptExecutor { /** * {@inheritDoc} */ @Override public boolean runScript(String name, String code) { try { if (LOG.isInfoEnabled()) { LOG.info("Running script: " + name); } runInitializerScript(name, code); } catch (Exception e) { LOG.error("Failed to run initializer script: " + e.getMessage(), e); } // continue unless rollback performed due to a script error return !ctx.getRollbackOnly(); } } private class RunonceScriptExecutor implements ScriptExecutor { private final String applicationName; private RunonceScriptExecutor(String applicationName) { this.applicationName = applicationName; } /** * {@inheritDoc} */ @Override public boolean runScript(String name, String code) { FxScriptRunInfo runInfo = new FxScriptRunInfo(System.currentTimeMillis(), applicationName, name); // TODO: write in tree cache for cluster support LocalScriptingCache.runOnceInfos.add(runInfo); try { if (LOG.isInfoEnabled()) { LOG.info("[" + applicationName + " :: " + name + "]"); } runInitializerScript(name, code); runInfo.endExecution(true); } catch (Exception e) { if (LOG.isWarnEnabled()) { LOG.warn("Failed to execute " + name + ": " + e.getMessage(), e); } runInfo.endExecution(false); final Throwable reported = e instanceof GenericScriptException ? e.getCause() : e; StringWriter sw = new StringWriter(); reported.printStackTrace(new PrintWriter(sw)); runInfo.setErrorMessage(sw.toString()); } try { Thread.sleep(50); //play nice for GUI list updates ... } catch (InterruptedException e) { //ignore } return !ctx.getRollbackOnly(); } } private void runInitializerScript(String name, String code) throws FxApplicationException { final UserTicket originalTicket = FxContext.getUserTicket(); FxContext.get().runAsSystem(); try { FxScriptBinding binding = new FxScriptBinding(); UserTicket ticket = ((UserTicketImpl) UserTicketImpl.getGuestTicket()).cloneAsGlobalSupervisor(); binding.setVariable("ticket", ticket); FxContext.get().overrideTicket(ticket); LocalScriptingCache.internal_runScript(name, binding, code); } finally { FxContext.get().stopRunAsSystem(); FxContext.get().overrideTicket(originalTicket); } } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public FxScriptResult runScript(String name, FxScriptBinding binding, String code) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptExecution); return LocalScriptingCache.internal_runScript(name, binding, code); } /** * {@inheritDoc} */ @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List<String[]> getAvailableScriptEngines() throws FxApplicationException { final List<String[]> ret = JDK6Scripting.getAvailableScriptEngines(); // Add gy extension f. Groovy ret.add(0, new String[] { "gy", "gy: Groovy v" + FxSharedUtils.getBundledGroovyVersion() + " (Bundled GroovyShell v" + FxSharedUtils.getBundledGroovyVersion() + ")" }); return ret; } /** * {@inheritDoc} */ @Override public FxScriptMapping loadScriptMapping(long scriptId) throws FxLoadException, SQLException { FxScriptMapping mapping; PreparedStatement ps_a = null, ps_t = null; String sql; Connection con = Database.getDbConnection(); List<FxScriptMappingEntry> e_ass; List<FxScriptMappingEntry> e_types; FxEnvironmentImpl environment = ((FxEnvironmentImpl) CacheAdmin.getEnvironment()).deepClone(); try { // 1 2 3 4 sql = "SELECT ASSIGNMENT,DERIVED_USAGE,ACTIVE,STYPE FROM " + TBL_SCRIPT_MAPPING_ASSIGN + " WHERE SCRIPT=?"; ps_a = con.prepareStatement(sql); sql = "SELECT TYPEDEF,DERIVED_USAGE,ACTIVE,STYPE FROM " + TBL_SCRIPT_MAPPING_TYPES + " WHERE SCRIPT=?"; ps_t = con.prepareStatement(sql); ResultSet rs; ps_a.setLong(1, scriptId); ps_t.setLong(1, scriptId); rs = ps_a.executeQuery(); e_ass = new ArrayList<FxScriptMappingEntry>(20); e_types = new ArrayList<FxScriptMappingEntry>(20); while (rs != null && rs.next()) { long[] derived; if (!rs.getBoolean(2)) derived = new long[0]; else { List<FxAssignment> ass = environment.getDerivedAssignments(rs.getLong(1)); derived = new long[ass.size()]; for (int i = 0; i < ass.size(); i++) derived[i] = ass.get(i).getId(); } e_ass.add(new FxScriptMappingEntry(FxScriptEvent.getById(rs.getLong(4)), scriptId, rs.getBoolean(3), rs.getBoolean(2), rs.getLong(1), derived)); } rs = ps_t.executeQuery(); while (rs != null && rs.next()) { long[] derived; if (!rs.getBoolean(2)) derived = new long[0]; else { List<FxType> types = environment.getType(rs.getLong(1)).getDerivedTypes(); derived = new long[types.size()]; for (int i = 0; i < types.size(); i++) derived[i] = types.get(i).getId(); } e_types.add(new FxScriptMappingEntry(FxScriptEvent.getById(rs.getLong(4)), scriptId, rs.getBoolean(3), rs.getBoolean(2), rs.getLong(1), derived)); } mapping = new FxScriptMapping(scriptId, e_types, e_ass); } catch (SQLException exc) { throw new FxLoadException(LOG, exc, "ex.scripting.mapping.load.failed", exc.getMessage()); } catch (FxNotFoundException e) { throw new FxLoadException(e); } finally { try { if (ps_t != null) ps_t.close(); } catch (SQLException e) { //ignore } Database.closeObjects(ScriptingEngineBean.class, con, ps_a); } return mapping; } /** * Checks if an assignment script mapping is consistent with * derived assignment script mappings that are already present in the DB. * * @param scriptId scriptId * @param assignmentId assignmentId * @param event script event * @param active active * @param derivedUsage derivedUsage * @throws FxEntryExistsException on errors */ public void checkAssignmentScriptConsistency(long scriptId, long assignmentId, FxScriptEvent event, boolean active, boolean derivedUsage) throws FxEntryExistsException { // in case script is set to active, // check if an active derived script assignment for this event already exists for this assignment if (active) { List<FxScriptMappingEntry> scriptMappings = CacheAdmin.getFilteredEnvironment() .getScriptMapping(scriptId).getMappedAssignments(); for (FxScriptMappingEntry sme : scriptMappings) { if (sme.isActive() && sme.getScriptEvent().getId() == event.getId()) { for (int i = 0; i < sme.getDerivedIds().length; i++) { if (sme.getDerivedIds()[i] == assignmentId) throw new FxEntryExistsException("ex.scripting.mapping.assign.derived.active.exists", CacheAdmin.getEnvironment().getScript(scriptId).getName(), event.getName(), CacheAdmin.getEnvironment().getAssignment(assignmentId).getXPath()); } } } // in case of derived usage and active, // check if an active script assignment for this event already exists for a sub-assignment if (derivedUsage) { for (FxAssignment a : CacheAdmin.getEnvironment().getDerivedAssignments(assignmentId)) { for (FxScriptMappingEntry sme : scriptMappings) { if (sme.getId() == a.getId() && sme.isActive() && sme.getScriptEvent().getId() == event.getId()) { throw new FxEntryExistsException("ex.scripting.mapping.assign.active.derived.exists", CacheAdmin.getEnvironment().getScript(scriptId).getName(), event.getName(), a.getXPath()); } } } } } } /** * Checks if a type script mapping is consistent with * derived type script mappings that are already present in the DB. * * @param scriptId scriptId * @param typeId typeId * @param event script event * @param active active * @param derivedUsage derivedUsage * @throws FxEntryExistsException if check fails */ public void checkTypeScriptConsistency(long scriptId, long typeId, FxScriptEvent event, boolean active, boolean derivedUsage) throws FxEntryExistsException { // in case script is set to active, // check if an active derived script assignment for this event already exists for this type if (active) { List<FxScriptMappingEntry> scriptMappings = CacheAdmin.getFilteredEnvironment() .getScriptMapping(scriptId).getMappedTypes(); for (FxScriptMappingEntry sme : scriptMappings) { if (sme.isActive() && sme.getScriptEvent().getId() == event.getId()) { for (int i = 0; i < sme.getDerivedIds().length; i++) { if (sme.getDerivedIds()[i] == typeId) throw new FxEntryExistsException("ex.scripting.mapping.type.derived.active.exists", CacheAdmin.getEnvironment().getScript(scriptId).getName(), event.getName(), CacheAdmin.getEnvironment().getType(typeId).getName()); } } } // in case of derived usage and active, // check if an active script assignment for this event already exists for a sub-type if (derivedUsage) { for (FxType t : CacheAdmin.getEnvironment().getType(typeId).getDerivedTypes()) { for (FxScriptMappingEntry sme : scriptMappings) { if (sme.getId() == t.getId() && sme.isActive() && sme.getScriptEvent().getId() == event.getId()) { throw new FxEntryExistsException("ex.scripting.mapping.type.active.derived.exists", CacheAdmin.getEnvironment().getScript(scriptId).getName(), event.getName(), t.getName()); } } } } } } /** * {@inheritDoc} * @since 3.1.2 */ @Override public FxScriptSchedule createScriptSchedule(FxScriptScheduleEdit scriptSchedule) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptExecution); FxScriptSchedule ss; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; //check script existence FxScriptInfo si = CacheAdmin.getEnvironment().getScript(scriptSchedule.getScriptId()); //check for valid script event if (si.getEvent() != FxScriptEvent.Manual) throw new FxConstraintViolationException("ex.scripting.schedule.wrongScriptEvent"); //check consistency checkScriptScheduleConsistency(scriptSchedule); try { // for groovy scripts set cached flag to true ss = new FxScriptSchedule(seq.getId(FxSystemSequencer.SCRIPTS), scriptSchedule.getScriptId(), scriptSchedule.getName(), scriptSchedule.isActive(), scriptSchedule.getStartTime(), scriptSchedule.getEndTime(), scriptSchedule.getRepeatInterval(), scriptSchedule.getRepeatTimes(), scriptSchedule.getCronString()); // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 5 6 7 8 9 sql = "INSERT INTO " + TBL_SCRIPT_SCHEDULES + " (ID,SCRIPT,SNAME,ACTIVE,STARTTIME,ENDTIME,REPEATINTERVAL,REPEATTIMES,CRONSTRING) VALUES (?,?,?,?,?,?,?,?,?)"; ps = con.prepareStatement(sql); ps.setLong(1, ss.getId()); ps.setLong(2, ss.getScriptId()); ps.setString(3, ss.getName()); ps.setBoolean(4, ss.isActive()); ps.setTimestamp(5, new Timestamp(ss.getStartTime().getTime())); ps.setTimestamp(6, ss.getEndTime() == null ? null : new Timestamp(ss.getEndTime().getTime())); ps.setLong(7, ss.getRepeatInterval()); ps.setInt(8, ss.getRepeatTimes()); ps.setString(9, ss.getCronString()); ps.executeUpdate(); if (EJBLookup.getTimerService().isInstalled()) { EJBLookup.getTimerService().scheduleScript(ss); } success = true; } catch (SQLException exc) { throw new FxCreateException(LOG, exc, "ex.scripting.schedule.create.failed", scriptSchedule.getName(), exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return ss; } /** * {@inheritDoc} * @since 3.1.2 */ @Override public FxScriptSchedule updateScriptSchedule(FxScriptScheduleEdit scriptSchedule) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptExecution); FxScriptSchedule ss; Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; //check script schedule existence CacheAdmin.getEnvironment().getScriptSchedule(scriptSchedule.getId()); //check script existence FxScriptInfo si = CacheAdmin.getEnvironment().getScript(scriptSchedule.getScriptId()); //check for valid script event if (si.getEvent() != FxScriptEvent.Manual) throw new FxConstraintViolationException("ex.scripting.schedule.wrongScriptEvent"); //check consistency checkScriptScheduleConsistency(scriptSchedule); try { ss = new FxScriptSchedule(scriptSchedule.getId(), scriptSchedule.getScriptId(), scriptSchedule.getName(), scriptSchedule.isActive(), scriptSchedule.getStartTime(), scriptSchedule.getEndTime(), scriptSchedule.getRepeatInterval(), scriptSchedule.getRepeatTimes(), scriptSchedule.getCronString()); // Obtain a database connection con = Database.getDbConnection(); // 1 2 3 4 5 6 7 8 9 sql = "UPDATE " + TBL_SCRIPT_SCHEDULES + " SET SNAME=?, ACTIVE=?, STARTTIME=?, ENDTIME=?, REPEATINTERVAL=?, REPEATTIMES=?, CRONSTRING=? WHERE ID=? AND SCRIPT=?"; ps = con.prepareStatement(sql); ps.setString(1, ss.getName()); ps.setBoolean(2, ss.isActive()); ps.setTimestamp(3, new Timestamp(ss.getStartTime().getTime())); ps.setTimestamp(4, ss.getEndTime() == null ? null : new Timestamp(ss.getEndTime().getTime())); ps.setLong(5, ss.getRepeatInterval()); ps.setInt(6, ss.getRepeatTimes()); ps.setString(7, ss.getCronString()); ps.setLong(8, ss.getId()); ps.setLong(9, ss.getScriptId()); ps.executeUpdate(); if (EJBLookup.getTimerService().isInstalled()) { EJBLookup.getTimerService().updateScriptSchedule(ss); } success = true; } catch (SQLException exc) { throw new FxUpdateException(LOG, exc, "ex.scripting.schedule.update.failed", scriptSchedule.getName(), exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } return ss; } /** * {@inheritDoc} * @since 3.1.2 */ @Override public void removeScriptSchedule(long scheduleId) throws FxApplicationException { FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptManagement); FxPermissionUtils.checkRole(FxContext.getUserTicket(), Role.ScriptExecution); Connection con = null; PreparedStatement ps = null; String sql; boolean success = false; FxScriptSchedule ss = CacheAdmin.getEnvironment().getScriptSchedule(scheduleId); try { // Obtain a database connection con = Database.getDbConnection(); sql = "DELETE FROM " + TBL_SCRIPT_SCHEDULES + " WHERE ID=?"; ps = con.prepareStatement(sql); ps.setLong(1, scheduleId); ps.executeUpdate(); ps.close(); if (EJBLookup.getTimerService().isInstalled()) { try { boolean deleteSuccess = EJBLookup.getTimerService().deleteScriptSchedule(ss); if (deleteSuccess) LOG.debug("Script schedule with id " + ss.getId() + " successfully deleted from scheduler"); else LOG.warn("Failed to delete script schedule with id " + ss.getId() + " from scheduler"); } catch (Exception e) { LOG.error("Error removing script schedule from scheduler", e); } } success = true; } catch (SQLException exc) { throw new FxRemoveException(LOG, exc, "ex.scripting.schedule.remove.failed", scheduleId, exc.getMessage()); } finally { Database.closeObjects(ScriptingEngineBean.class, con, ps); if (!success) EJBUtils.rollback(ctx); else StructureLoader.reloadScripting(FxContext.get().getDivisionId()); } } private void checkScriptScheduleConsistency(FxScriptScheduleEdit scriptSchedule) throws FxInvalidParameterException { // repeat times must be either unbounded or >0 if (scriptSchedule.getRepeatTimes() < 0 && scriptSchedule.getRepeatTimes() != FxScriptSchedule.REPEAT_TIMES_UNBOUNDED) throw new FxInvalidParameterException("repeatTimes", "ex.scripting.schedule.parameter.repeatTimes.invalid"); // if and end time is specified, repeat times must be unbounded if (scriptSchedule.getEndTime() != null && scriptSchedule.getRepeatTimes() != FxScriptSchedule.REPEAT_TIMES_UNBOUNDED) throw new FxInvalidParameterException("repeatTimes", "ex.scripting.schedule.parameter.endTimeAndRepeatNotUnbounded"); // if a cron string is specified, repeat times must be unbounded if (StringUtils.isNotBlank(scriptSchedule.getCronString()) && scriptSchedule.getRepeatTimes() != FxScriptSchedule.REPEAT_TIMES_UNBOUNDED) throw new FxInvalidParameterException("repeatTimes", "ex.scripting.schedule.parameter.cronStringAndRepeatNotUnbounded"); // if a cron string is specified, repeat interval must be < 0 if (StringUtils.isNotBlank(scriptSchedule.getCronString()) && !(scriptSchedule.getRepeatInterval() < 0)) throw new FxInvalidParameterException("repeatTimes", "ex.scripting.schedule.parameter.cronStringAndRepeatInterval"); // invalid cron String if (StringUtils.isNotBlank(scriptSchedule.getCronString())) { EJBLookup.getTimerService().parseCronString(scriptSchedule.getCronString()); } //if start and end time are specified, end time must be after start time if (scriptSchedule.getStartTime() != null && scriptSchedule.getEndTime() != null && !scriptSchedule.getEndTime().after(scriptSchedule.getStartTime())) throw new FxInvalidParameterException("endTime", "ex.scripting.schedule.parameter.endTimeAfterStartTime"); } }