PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/groovy/com/onresolve/jira/groovy/ScriptManagerImpl.groovy

https://bitbucket.org/sorin/jira-plugin-intellij
Groovy | 355 lines | 241 code | 49 blank | 65 comment | 26 complexity | 413a3ec808981bd4c8f3a6336b23be3e MD5 | raw file
  1. package com.onresolve.jira.groovy
  2. import com.atlassian.core.action.ActionUtils
  3. import com.atlassian.core.ofbiz.CoreFactory
  4. import com.atlassian.core.ofbiz.util.OFBizPropertyUtils
  5. import com.atlassian.core.util.ClassLoaderUtils
  6. import com.atlassian.event.api.EventPublisher
  7. import com.atlassian.jira.ComponentManager
  8. import com.atlassian.jira.InfrastructureException
  9. import com.atlassian.jira.action.admin.ListenerDelete
  10. import com.atlassian.jira.component.ComponentAccessor
  11. import com.atlassian.jira.config.properties.PropertiesManager
  12. import com.atlassian.jira.event.ListenerManager
  13. import com.atlassian.jira.extension.Startable
  14. import com.atlassian.jira.util.collect.MapBuilder
  15. import com.atlassian.jira.util.json.JSONArray
  16. import com.atlassian.jira.util.json.JSONObject
  17. import com.onresolve.jira.groovy.canned.util.JoinClassLoader
  18. import com.onresolve.jira.groovy.customfield.CustomFieldConfiguration
  19. import com.onresolve.jira.groovy.listener.GroovyListener
  20. import com.onresolve.jira.groovy.listener.ScriptRunnerUberListener
  21. import com.opensymphony.module.propertyset.PropertySet
  22. import org.apache.log4j.Category
  23. import org.ofbiz.core.entity.GenericValue
  24. import org.springframework.beans.factory.DisposableBean
  25. import org.springframework.beans.factory.InitializingBean
  26. import webwork.dispatcher.ActionResult
  27. import com.onresolve.jira.groovy.canned.CannedScript
  28. import org.codehaus.groovy.control.CompilerConfiguration
  29. class ScriptManagerImpl implements ScriptManager, InitializingBean, DisposableBean, Startable {
  30. private Properties props
  31. private GroovyClassLoader gclx;
  32. PropertiesManager pm = ComponentManager.getComponentInstanceOfType(PropertiesManager.class)
  33. public static String CONFIG_CUSTOMFIELDS = "com.onresolve.jira.groovy.groovyrunner:customfields"
  34. public static String CONFIG_LISTENERS = "com.onresolve.jira.groovy.groovyrunner:groovyrunner"
  35. Category log = Category.getInstance(ScriptManagerImpl.class)
  36. private final String LISTENER_NAME = "Script Runner Uber-Listener";
  37. private final EventPublisher eventPublisher;
  38. // ref to listener class that we can unregister if we need to
  39. def listener;
  40. ScriptManagerImpl(EventPublisher eventPublisher) {
  41. this.eventPublisher = eventPublisher
  42. }
  43. public Properties getProperties() {
  44. if (!props) {
  45. log.debug ("Loading properties")
  46. props = new Properties(System.getProperties());
  47. InputStream propsResource = ClassLoaderUtils.getResourceAsStream("com/onresolve/jira/groovy/groovyRunner.properties", CannedScriptRunner.class);
  48. if (propsResource == null) throw new InfrastructureException("Could not locate groovyRunner.properties in classpath");
  49. props.load(propsResource);
  50. }
  51. return props
  52. }
  53. public GroovyClassLoader getGcl() {
  54. if (!gclx) {
  55. def webAppClassLoader = ComponentAccessor.class.getClassLoader()
  56. def resourceDirs = System.getProperty("plugin.resource.directories")
  57. def joinClassLoader
  58. if (resourceDirs) {
  59. def urls = resourceDirs?.split(/,/)?.collect {new File(it).toURI().toURL()}
  60. def altResClassLoader = new URLClassLoader(urls as URL[], webAppClassLoader)
  61. // if this way round: this.class.getClassLoader(), altResClassLoader
  62. // we get classcast exceptions, eg:
  63. // GroovyCastException: Cannot cast object 'com.atlassian.jira.config.DefaultConstantsManager@f4af9b'
  64. // with class 'com.atlassian.jira.config.DefaultConstantsManager' to class 'com.atlassian.jira.config.ConstantsManager'
  65. // we can't get class reloading, because the ones in the bundle are found first,
  66. // unless we remove all the classes from the build
  67. joinClassLoader = new JoinClassLoader(altResClassLoader, this.class.getClassLoader());
  68. // if the other way round we get the comment below
  69. }
  70. else {
  71. // todo: this is likely wrong for getting OSGi components using ComponentManager.getComponentInstanceOfType
  72. // classcast exceptions
  73. joinClassLoader = new JoinClassLoader(webAppClassLoader, this.class.getClassLoader());
  74. }
  75. GroovyClassLoader gcl = new GroovyClassLoader(joinClassLoader)
  76. gcl.setShouldRecompile(true);
  77. gcl.setResourceLoader(new JiraGroovyResourceLoader(gcl, this))
  78. gclx = gcl
  79. }
  80. return gclx;
  81. }
  82. public void setupConfig() {
  83. CustomFieldConfiguration configuration = new CustomFieldConfiguration()
  84. configuration.setId(10080)
  85. configuration.setScript("return \"some new bollo\"")
  86. configuration.setTemplate("\$!value")
  87. saveConfig(configuration.asJsonString())
  88. // saveConfig(new JSONObject(
  89. // ["10070":
  90. // [
  91. // script: "return \"some bollo\"",
  92. // template: "\$!value",
  93. // templateFile: "",
  94. // ]
  95. // ]))
  96. }
  97. public void saveConfig(JSONObject configs) {
  98. pm.getPropertySet().setText(CONFIG_CUSTOMFIELDS, (configs as JSONObject).toString())
  99. }
  100. public Map<Long,CustomFieldConfiguration> getConfigs() {
  101. String prop = pm.getPropertySet().getText(CONFIG_CUSTOMFIELDS) ?: [:]
  102. // log.debug("getConfigs: $prop")
  103. JSONObject jsonObject = new JSONObject(prop)
  104. Map<Long,CustomFieldConfiguration> rt = [:]
  105. Map map = getAsActualType(jsonObject) as Map
  106. map.each {String k, Map v ->
  107. CustomFieldConfiguration cfc = new CustomFieldConfiguration()
  108. cfc.setId(k as Long)
  109. cfc.setScript(v.get(CustomFieldConfiguration.SCRIPT) as String)
  110. cfc.setTemplate(v.get(CustomFieldConfiguration.TEMPLATE) as String)
  111. cfc.setModelTemplate(v.get(CustomFieldConfiguration.MODEL_TEMPLATE) as String)
  112. cfc.setCustomTemplate(v.get(CustomFieldConfiguration.CUSTOM_TEMPLATE) as String)
  113. cfc.setScriptFile(v.get(CustomFieldConfiguration.SCRIPT_FILE) as String)
  114. rt.put(k as Long, cfc)
  115. }
  116. return rt
  117. }
  118. // todo: exists in 3 places now
  119. // todo: this is the version that I think is fixed for nulls
  120. public Object getAsActualType(Object o) {
  121. Object n = null
  122. if (o instanceof JSONArray) {
  123. n = []
  124. (0..o.length() - 1).each {int i ->
  125. n.add getAsActualType(o.get(i))
  126. }
  127. }
  128. else if (o instanceof JSONObject) {
  129. n = [:]
  130. o.keys().each {String k ->
  131. if (o.get(k) == JSONObject.NULL) {
  132. n.put(k, null)
  133. }
  134. else {
  135. n.put(k, getAsActualType(o.get(k)))
  136. }
  137. }
  138. }
  139. else {
  140. return o
  141. }
  142. return n
  143. }
  144. public CustomFieldConfiguration getConfigFor(Long cfId) {
  145. def configs = getConfigs()
  146. configs.containsKey(cfId) ? configs.get(cfId) : null
  147. /*
  148. String prop = pm.getPropertySet().getText(CONFIG_CUSTOMFIELDS)
  149. // log.debug("getConfigFor: $prop")
  150. if (!prop) {
  151. return null
  152. }
  153. try {
  154. JSONObject map = new JSONObject(prop)
  155. Map configs = getAsActualType(map) as Map
  156. if (configs.size() == 0) {
  157. return null
  158. }
  159. if (configs.containsKey(cfId.toString())) {
  160. // todo: this is bullshit
  161. return new CustomFieldConfiguration(new JSONObject(configs[cfId.toString()] as Map).toString())
  162. }
  163. else {
  164. return null
  165. }
  166. }
  167. catch (Exception e) {
  168. log.error ("property parse exception: ${e.message}", e)
  169. return null
  170. }
  171. */
  172. }
  173. public void deleteLegacyUberListener() {
  174. // move groovy listeners to properties format
  175. final Collection<GenericValue> listenerConfigs = CoreFactory.getGenericDelegator().findAll("ListenerConfig");
  176. PropertiesManager pm = ComponentManager.getComponentInstanceOfType(PropertiesManager)
  177. PropertySet ps = pm.getPropertySet()
  178. def cannedScriptListener = new CannedScriptListener(this)
  179. List configs = getListenerConfigs()
  180. // log.debug("configs: $configs")
  181. def allEvents = ComponentManager.getInstance().getEventTypeManager().getEventTypesMap().keySet()*.toString()
  182. listenerConfigs.each {gv ->
  183. if (gv.get("clazz") == "com.onresolve.jira.groovy.listener.GroovyListener") {
  184. Map params = getListenerParams(gv)
  185. log.debug("move class: " + params.get("class"))
  186. configs.add(
  187. [
  188. events:allEvents,
  189. projects: [""],
  190. clazz: "com.onresolve.jira.groovy.canned.workflow.listeners.CustomListener",
  191. params: [
  192. clazz: params.get("class"),
  193. ]
  194. ]
  195. )
  196. log.debug("Save: " + (configs as JSONArray).toString())
  197. cannedScriptListener.saveConfig(configs)
  198. deleteListener(gv.get("name") as String, GroovyListener.class)
  199. }
  200. }
  201. // delete both types of old listeners
  202. deleteListener(LISTENER_NAME, ScriptRunnerUberListener.class)
  203. }
  204. private deleteListener(String listenerName, Class clazz) {
  205. ListenerManager listenerManager = ComponentManager.getComponentInstanceOfType(ListenerManager.class)
  206. listenerManager.refresh()
  207. if (listenerManager.getListeners().containsKey(listenerName)) {
  208. if (listenerManager.respondsTo("deleteListener")) {
  209. listenerManager.deleteListener(clazz)
  210. }
  211. else {
  212. ActionResult aResult = CoreFactory.getActionDispatcher().execute(ListenerDelete.class.getName(),
  213. ["name": listenerName, "clazz": clazz.getName()])
  214. ActionUtils.checkForErrors(aResult);
  215. }
  216. log.info "Deleted listener $listenerName"
  217. }
  218. else {
  219. log.debug("Listener $listenerName wasn't present")
  220. }
  221. }
  222. private Map getListenerParams(GenericValue listenerConfig) {
  223. final PropertySet ps = OFBizPropertyUtils.getPropertySet(listenerConfig);
  224. final Collection<String> paramKeys = ps.getKeys(PropertySet.STRING);
  225. final MapBuilder<String, String> params = MapBuilder.newBuilder();
  226. for (final String key: paramKeys) {
  227. params.add(key, ps.getString(key));
  228. }
  229. return params.toMap()
  230. }
  231. void afterPropertiesSet() {
  232. // migrate properties to new format
  233. pm.getPropertySet().setText(CONFIG_LISTENERS, (getListenerConfigs() as JSONArray).toString())
  234. // attempt to init the gse, because if it's called from the dash we can end up with it null
  235. // possibly soemthing to do with classloaders
  236. CannedScriptRunner.getGse()
  237. // delete legacy listener
  238. try {
  239. deleteLegacyUberListener()
  240. }
  241. catch (Exception e) {
  242. log.debug "Listeners weren't ready, not a problem."
  243. }
  244. // register new listener
  245. listener = new ScriptRunnerUberListener(this)
  246. eventPublisher.register(listener)
  247. }
  248. /**
  249. * Called when the plugin is being disabled or removed.
  250. * @throws Exception
  251. */
  252. @Override
  253. public void destroy() throws Exception {
  254. // unregister ourselves with the EventPublisher
  255. if (listener) {
  256. log.debug("Unregistering previously registered listener instance")
  257. eventPublisher.unregister(listener);
  258. }
  259. }
  260. void start() {
  261. try {
  262. deleteLegacyUberListener()
  263. }
  264. catch (Exception e) {
  265. log.warn ("Error migrating listeners to new format", e)
  266. }
  267. }
  268. public List getListenerConfigs() {
  269. PropertiesManager pm = ComponentManager.getComponentInstanceOfType(PropertiesManager)
  270. String prop = pm.getPropertySet().getText(CONFIG_LISTENERS)
  271. if (!prop) {
  272. return []
  273. }
  274. try {
  275. // old storage method
  276. // if this is a Config parse it and convert to JSON
  277. if (prop.startsWith("config")) {
  278. log.debug("prop now: " + prop.class)
  279. def configSlurper = getGcl().loadClass("groovy.util.ConfigSlurper", false, true)
  280. def restoredCo = configSlurper.newInstance().parse(prop as String)
  281. log.debug("restoredCo: $restoredCo")
  282. return restoredCo?.config as List
  283. }
  284. else {
  285. // convert params to proper objects
  286. JSONArray array = new JSONArray(prop)
  287. if (array.length() == 0) {
  288. return []
  289. }
  290. List list = getAsActualType(array) as List
  291. return list
  292. }
  293. }
  294. catch (Exception e) {
  295. log.error ("property parse exception: ${e.message}")
  296. return []
  297. }
  298. }
  299. public Map executeScript(String clsName, Map params) {
  300. log.warn ("Execute canned script $clsName")
  301. return new CannedScriptRunner(this).runCannedScript(clsName, params);
  302. // def instance = Class.forName(clsName).newInstance() as CannedScript
  303. // instance.doScript(params)
  304. }
  305. }