/src/main/groovy/com/onresolve/jira/groovy/GroovyCustomField.groovy
Groovy | 415 lines | 282 code | 68 blank | 65 comment | 36 complexity | d49b446fa61b399e0d49ea5eedc43cab MD5 | raw file
- package com.onresolve.jira.groovy;
- import com.atlassian.jira.ComponentManager
- import com.atlassian.jira.ManagerFactory
- import com.atlassian.jira.component.ComponentAccessor
- import com.atlassian.jira.config.properties.ApplicationProperties
- import com.atlassian.jira.datetime.DateTimeFormatterFactory
- import com.atlassian.jira.datetime.DateTimeStyle
- import com.atlassian.jira.issue.Issue
- import com.atlassian.jira.issue.customfields.SortableCustomField
- import com.atlassian.jira.issue.customfields.converters.StringConverter
- import com.atlassian.jira.issue.customfields.impl.CalculatedCFType
- import com.atlassian.jira.issue.customfields.impl.FieldValidationException
- import com.atlassian.jira.issue.fields.CustomField
- import com.atlassian.jira.issue.fields.config.FieldConfig
- import com.atlassian.jira.issue.fields.config.manager.FieldConfigSchemeManager
- import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem
- import com.atlassian.jira.security.JiraAuthenticationContext
- import com.atlassian.jira.security.PermissionManager
- import com.atlassian.jira.security.Permissions
- import com.atlassian.jira.util.DateFieldFormat
- import com.atlassian.jira.util.JiraVelocityUtils
- import com.atlassian.jira.util.velocity.NumberTool
- import com.atlassian.velocity.VelocityManager
- import com.onresolve.jira.groovy.customfield.CustomFieldConfiguration
- import java.security.MessageDigest
- import javax.script.Bindings
- import javax.script.ScriptEngine
- import javax.script.SimpleBindings
- import org.apache.log4j.Category
- import com.atlassian.jira.config.properties.APKeys
- import com.atlassian.jira.issue.fields.config.FieldConfigScheme
- /**
- * User: echlinj
- * Date: 04-Apr-2008
- * Time: 17:29:41
- */
- public class GroovyCustomField extends CalculatedCFType implements SortableCustomField {
- private final StringConverter stringConverter;
- private Category log = Category.getInstance(GroovyCustomField.class);
- private final ApplicationProperties applicationProperties;
- private String script;
- private String scriptFile;
- private String modelTemplate;
- private String customTemplate;
- private String exceptionMessage;
- private final PermissionManager permissionManager;
- private final JiraAuthenticationContext authenticationContext;
- public boolean previewMode = false
- ScriptManager scriptManager
- public GroovyCustomField(StringConverter stringConverter,
- ApplicationProperties applicationProperties,
- final PermissionManager permissionManager, final JiraAuthenticationContext authenticationContext,
- final ScriptManager scriptManager)
- {
- super();
- this.stringConverter = stringConverter;
- this.applicationProperties = applicationProperties;
- this.permissionManager = permissionManager;
- this.authenticationContext = authenticationContext;
- this.scriptManager = scriptManager
- }
- public Object getCustomFieldValue() {
- return "bazooka";
- }
-
- // @Override
- // public List<FieldIndexer> getRelatedIndexers(CustomField customField) {
- // // seems to only be called when the cache is refreshed.
- // log.warn("GroovyCustomField.getRelatedIndexers");
- // return EasyList.build(new MultiGroupCustomFieldIndexer(fieldVisibilityManager, customField, multiGroupConverter));
- // }
- /*
- * This is what is called by the indexer, but only if an indexer is setup for this CF.
- * Also called 3 times per issue, hence caching
- */
- public Object getValueFromIssue(CustomField customField, Issue issue) {
- // this is the only place the groovy should happen
- // run velocity template
- setExceptionMessage(null);
- String fieldIssueId = getFieldIssueId(customField, issue);
- if (result.get().containsKey(fieldIssueId)) {
- // if (log.isDebugEnabled()) {
- // log.debug("avoided script eval for issue: " + issue.getKey() + " cf: " + customField.getName());
- // }
- return result.get().get(fieldIssueId);
- }
- // scriptManager.setupConfig();
- CustomFieldConfiguration config = getRelevantConfig(customField, issue);
- // preview mode the script will be set
- config = setConfigForPreview(config)
- if (config == null || (! config.getScript() && ! config.getScriptFile())) {
- // no idea why this is called when it does not apply
- // if (log.isDebugEnabled()) {
- // log.debug("Field not set up correctly, or this field does not apply to this issue: " + issue.getKey());
- // }
- return null;
- }
- Bindings bindings = new SimpleBindings();
- def customFieldManager = ComponentAccessor.getCustomFieldManager()
- def getCustomFieldValue = {Object cfId ->
- def customFields = customFieldManager.getCustomFieldObjects(issue)
- def cf
- if (cfId instanceof String) {
- cf = customFields.find {it.name.equals(cfId)}
- }
- else {
- cf = customFields.find {it.idAsLong == cfId}
- }
- if (!cf) {
- log.error("getCustomFieldValue called from script: No field with ID or name $cfId")
- return null
- }
- return issue.getCustomFieldValue(cf)
- }
- bindings.put("issue", issue);
- bindings.put("log", log);
- bindings.put("componentManager", ComponentManager.getInstance());
- bindings.put("getCustomFieldValue", getCustomFieldValue);
- // use a base class here...?
- ScriptEngine gse = CannedScriptRunner.getGse();
- try {
- Object eval;
- def scriptFile = config.getScriptFile()
- if (scriptFile) {
- String scriptText = getTextFromFileOrUrl(scriptFile)
- eval = gse.eval(scriptText, bindings);
- }
- else {
- eval = gse.eval(config.getScript(), bindings);
- // for (int i = 0; i < 10; i++) {
- // log.debug("parse: " + i);
- // gse.eval(config.getScript(), bindings);
- // }
- }
- // would like to use GroovyShell here to set a base class for the script but it's much slower than script engine,
- // unless we implement the caching ourselves as in behaviours
- if (eval != null) {
- if (log.isDebugEnabled()) {
- log.debug("Script was executed for cf: " + customField.getName() +
- " and issue " + issue.getKey() +
- " and returned: " + eval.toString());
- }
- }
- // don't cache result when run through the previewer
- if (! getPreviewMode()) {
- result.get().put(fieldIssueId, eval);
- }
- return eval;
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- setExceptionMessage(e.getMessage());
- }
- return null;
- }
- private getTextFromFileOrUrl(String scriptFile) {
- String scriptText
- try {
- scriptText = new URL(scriptFile).text
- }
- catch (MalformedURLException e) {
- FileInputStream fileInputStream = new FileInputStream(scriptFile);
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
- scriptText = bufferedReader.text
- }
- scriptText
- }
- private setConfigForPreview(CustomFieldConfiguration config) {
- if (getPreviewMode()) {
- // if we don't have a configuration then it's because this issue is not in the context of this custom field
- // which is fine for previewing, just use a dummy one
- if (!config) {
- config = new CustomFieldConfiguration()
- }
-
- config.setScript(script);
- config.setScriptFile(scriptFile)
- config.setModelTemplate(modelTemplate)
- config.setCustomTemplate(customTemplate)
- }
- return config
- }
- private String getFieldIssueId(CustomField customField, Issue issue) {
- if (issue != null && issue.getId() != null && customField != null) {
- // some measure of caching. At worst this is going to cause the script to run more often than we'd like, not less
- // return issue.getId() << 8 + customField.getIdAsLong() << 16 + (long) (System.currentTimeMillis() / 1000);
- // better way but may be slower:
- MessageDigest digest = MessageDigest.getInstance("MD5")
- digest.update((issue.id.toString() + customField.id + ((long) (System.currentTimeMillis() / 1000)).toString()).bytes)
- BigInteger big = new BigInteger(1,digest.digest())
- String md5 = big.toString(16).padLeft(32,"0")
- return md5
- }
- else {
- return null;
- }
- }
- private CustomFieldConfiguration getRelevantConfig(CustomField customField, Issue issue) {
- FieldConfigSchemeManager fieldConfigSchemeManager = ComponentManager.getComponentInstanceOfType(FieldConfigSchemeManager.class);
- FieldConfigScheme relevantConfig = fieldConfigSchemeManager.getRelevantConfigScheme(issue, customField);
- if (relevantConfig == null) {
- return null;
- }
- // def scriptManager = ComponentManager.getComponentInstanceOfType(ScriptManager.class)
- CustomFieldConfiguration configFor = scriptManager.getConfigFor(relevantConfig.getId());
- return configFor;
- }
- public String getStringFromSingularObject(Object value)
- {
- log.debug("getStringFromSingularObject called with value: " + value);
- // todo: check the right type or subvert?
- // assertObjectImplementsType(String.class, value);
- // return stringConverter.getString((String) value);
- // return string rep of what was produced by getvaluefromissue
- return value.toString();
- }
- /**
- * Called when updating
- */
- public Object getSingularObjectFromString(String string) throws FieldValidationException
- {
- // log.debug("getSingularObjectFromString: string: " + string);
- // could be number type
- return stringConverter.getObject(string);
- }
- @Override
- @SuppressWarnings("unchecked")
- public Map getVelocityParameters(Issue issue, CustomField field, FieldLayoutItem fieldLayoutItem) {
- Map map = super.getVelocityParameters(issue, field, fieldLayoutItem);
- map.put("issue", issue);
- map.put("field", field);
- map.put("fieldLayoutItem", fieldLayoutItem);
- map.put("groovyCustomField", this);
- return map;
- }
- @SuppressWarnings("unchecked")
- public Map getRealVelocityParameters(Issue issue, CustomField field, FieldLayoutItem fieldLayoutItem) {
- String fieldIssueId = getFieldIssueId(field, issue);
- // log.debug("fieldIssueId: " + fieldIssueId);
- // log.debug("threadlocal result: " + result.get().get(fieldIssueId));
- // Map map = super.getVelocityParameters(issue, field, fieldLayoutItem);
- Map<String, Object> map = JiraVelocityUtils.createVelocityParams(ComponentManager.getInstance().getJiraAuthenticationContext());
- map.put("groovyCustomField", this);
- map.put("issue", issue);
- map.put("customField", field);
- map.put("field", field);
- map.put("fieldLayoutItem", fieldLayoutItem);
- map.put("value", getValueFromIssue(field, issue));
- // map.put ("velocityParams", map);
- map.put("numberTool", new NumberTool(getI18nBean().getLocale()));
- map.put("dateFieldFormat", ComponentAccessor.getComponentOfType(DateFieldFormat.class));
- DateTimeFormatterFactory dateTimeFormatterFactory = ComponentAccessor.getComponentOfType(DateTimeFormatterFactory.class);
- map.put("iso8601Formatter", dateTimeFormatterFactory.formatter().withStyle(DateTimeStyle.ISO_8601_DATE).withSystemZone());
- map.put("datePickerFormatter", dateTimeFormatterFactory.formatter().forLoggedInUser().withStyle(DateTimeStyle.DATE_TIME_PICKER));
- map.put("titleFormatter", dateTimeFormatterFactory.formatter().withStyle(DateTimeStyle.COMPLETE));
- map.put("hasAdminPermission", permissionManager.hasPermission(Permissions.ADMINISTER, authenticationContext.getUser()));
- // not right for 4.4
- //map.put("userformat", userFormatManager.getUserFormat(ProfileLinkUserFormat.TYPE));
- // not sure, need to test dates
- // map.put("outlookdate", ComponentManager.getInstance().getJiraAuthenticationContext().getOutlookDate());
- // result.get().remove(fieldIssueId);
- return map;
- }
- public String getHtml(Map startingParams)
- {
- try
- {
- CustomField customField = (CustomField) startingParams.get("field");
- Issue issue = (Issue) startingParams.get("issue");
- CustomFieldConfiguration config = getRelevantConfig(customField, issue);
- // preview mode the script will be set
- config = setConfigForPreview(config)
- if (config == null) {
- return "Field not set up correctly";
- }
- if (! (config.getScript() || config.getScriptFile())) {
- return "Field not set up correctly - script is not defined";
- }
- String viewTemplate = config.getViewTemplate();
- def customTemplate = config.getCustomTemplate()
- if (!viewTemplate && !customTemplate) {
- return "Field not set up correctly - view template is not defined";
- }
- VelocityManager velocityManager = ManagerFactory.getVelocityManager();
- String encodedBody
- if (config.getModelTemplate() == "custom") {
- encodedBody = velocityManager.getEncodedBodyForContent (customTemplate, applicationProperties.getString(APKeys.JIRA_BASEURL), startingParams);
- }
- else {
- encodedBody = velocityManager.getEncodedBody("", viewTemplate, applicationProperties.getEncoding(), startingParams);
- }
- if (log.isDebugEnabled()) {
- log.debug("Encoded body: " + encodedBody);
- }
- return encodedBody;
- }
- catch (Throwable e)
- {
- log.error("Error while rendering velocity template for ....", e);
- }
- return "Error in getting html";
- }
- public String doVelocityMerge(Map params) {
-
- return getHtml(params);
- }
- public int compare(Object customFieldObjectValue1, Object customFieldObjectValue2, FieldConfig fieldConfig)
- {
- return ((String) customFieldObjectValue1).compareTo((String) customFieldObjectValue2);
- }
- public void setScript(String script) {
- this.script = script;
- }
- public void setScriptFile(String scriptFile) {
- this.scriptFile = scriptFile;
- }
- public void setModelTemplate(String modelTemplate) {
- this.modelTemplate = modelTemplate;
- }
- public void setCustomTemplate(String customTemplate) {
- this.customTemplate = customTemplate;
- }
- public String getExceptionMessage() {
- return exceptionMessage;
- }
- public void setExceptionMessage(String exceptionMessage) {
- this.exceptionMessage = exceptionMessage;
- }
- private String getBaseUrl()
- {
- // final ApplicationProperties applicationProperties = ComponentManager.getInstance().getApplicationProperties();
- // final VelocityRequestContext velocityRequestContext = new VelocityRequestContextFactory(applicationProperties).getJiraVelocityRequestContext();
- // return velocityRequestContext.getBaseUrl();
- return "";
- }
- /**
- * Needs to be a map as the indexer will otherwise get the same value for all issues
- */
- private static ThreadLocal<Map<String,Object>> result = new ThreadLocal<Map<String,Object>>() {
- @Override
- protected Map<String,Object> initialValue() {
- return [:];
- }
- };
-
- public void setPreviewMode(boolean previewMode) {
- this.previewMode = previewMode
- }
-
- public boolean getPreviewMode() {
- return previewMode
- }
-
- }