org.jdom.IllegalDataException

There are no available Samebug tips for this exception. Do you have an idea how to solve this issue? A short tip would help users who saw this issue last week.

  • We detected an issue when the importer von Fogbugz fails due to an ending CDATA section in the case description. The Snippet in the Fogbugz case is as follows: <![CDATA[ 662-45 32 49 ]]> The importer fails with an error, and the log reports the following: 2014-01-22 17:00:26,889 ERROR - Unexpected failure occurred. Importer will stop immediately. Data maybe in an unstable state org.jdom.IllegalDataException: The data "........... (here I've cut our case description, it contains ]]>)" " is not legal for a JDOM CDATA section: CDATA cannot internally contain a CDATA ending delimiter (]]>). at org.jdom.CDATA.setText(CDATA.java:121) at org.jdom.CDATA.<init>(CDATA.java:95) at org.jdom.DefaultJDOMFactory.cdata(DefaultJDOMFactory.java:97) at org.jdom.input.SAXHandler.flushCharacters(SAXHandler.java:652) at org.jdom.input.SAXHandler.flushCharacters(SAXHandler.java:623) at org.jdom.input.SAXHandler.endElement(SAXHandler.java:678) at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source) at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanEndElement(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XMLParser.parse(Unknown Source) at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source) at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source) at org.jdom.input.SAXBuilder.build(SAXBuilder.java:453) at org.jdom.input.SAXBuilder.build(SAXBuilder.java:770) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzClient.getWithToken(FogBugzClient.java:223) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzClient.getCases(FogBugzClient.java:275) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzHostedDataBean.getIssuesIterator(FogBugzHostedDataBean.java:132) at com.atlassian.jira.plugins.importer.imports.importer.impl.DefaultJiraDataImporter.importIssues(DefaultJiraDataImporter.java:833) at com.atlassian.jira.plugins.importer.imports.importer.impl.DefaultJiraDataImporter.doImport(DefaultJiraDataImporter.java:428) at com.atlassian.jira.plugins.importer.imports.importer.impl.ImporterCallable.call(ImporterCallable.java:26) at com.atlassian.jira.plugins.importer.imports.importer.impl.ImporterCallable.call(ImporterCallable.java:15) at com.atlassian.jira.task.TaskManagerImpl$TaskCallableDecorator.call(TaskManagerImpl.java:374) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at com.atlassian.jira.task.ForkedThreadExecutor$ForkedRunnableDecorator.run(ForkedThreadExecutor.java:250) at java.lang.Thread.run(Thread.java:662) Would be great if you could fix this, thanks!
    via by Martin Lechner,
  • We detected an issue when the importer von Fogbugz fails due to an ending CDATA section in the case description. The Snippet in the Fogbugz case is as follows: <![CDATA[ 662-45 32 49 ]]> The importer fails with an error, and the log reports the following: 2014-01-22 17:00:26,889 ERROR - Unexpected failure occurred. Importer will stop immediately. Data maybe in an unstable state org.jdom.IllegalDataException: The data "........... (here I've cut our case description, it contains ]]>)" " is not legal for a JDOM CDATA section: CDATA cannot internally contain a CDATA ending delimiter (]]>). at org.jdom.CDATA.setText(CDATA.java:121) at org.jdom.CDATA.<init>(CDATA.java:95) at org.jdom.DefaultJDOMFactory.cdata(DefaultJDOMFactory.java:97) at org.jdom.input.SAXHandler.flushCharacters(SAXHandler.java:652) at org.jdom.input.SAXHandler.flushCharacters(SAXHandler.java:623) at org.jdom.input.SAXHandler.endElement(SAXHandler.java:678) at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source) at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanEndElement(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XMLParser.parse(Unknown Source) at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source) at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source) at org.jdom.input.SAXBuilder.build(SAXBuilder.java:453) at org.jdom.input.SAXBuilder.build(SAXBuilder.java:770) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzClient.getWithToken(FogBugzClient.java:223) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzClient.getCases(FogBugzClient.java:275) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzHostedDataBean.getIssuesIterator(FogBugzHostedDataBean.java:132) at com.atlassian.jira.plugins.importer.imports.importer.impl.DefaultJiraDataImporter.importIssues(DefaultJiraDataImporter.java:833) at com.atlassian.jira.plugins.importer.imports.importer.impl.DefaultJiraDataImporter.doImport(DefaultJiraDataImporter.java:428) at com.atlassian.jira.plugins.importer.imports.importer.impl.ImporterCallable.call(ImporterCallable.java:26) at com.atlassian.jira.plugins.importer.imports.importer.impl.ImporterCallable.call(ImporterCallable.java:15) at com.atlassian.jira.task.TaskManagerImpl$TaskCallableDecorator.call(TaskManagerImpl.java:374) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at com.atlassian.jira.task.ForkedThreadExecutor$ForkedRunnableDecorator.run(ForkedThreadExecutor.java:250) at java.lang.Thread.run(Thread.java:662) Would be great if you could fix this, thanks!
    via by Martin Lechner,
  • Strange console output
    via by Unknown author,
  • Error during dispatching of java.awt.event.MouseEvent[MOUSE_RELEASED,(524,555),button=1,modifiers=Button1,clickCount=1] on dialog11: The data "Index: atlassian-ide-plugin.xml =================================================================== --- atlassian-ide-plugin.xml (revision 70395) +++ atlassian-ide-plugin.xml (working copy) @@ -55,6 +55,11 @@ <url>https://svn.atlassian.com/atlaseye</url> <isFisheyeInstance>true</isFisheyeInstance> </crucible> + <jira> + <server-id>fcee551d-cd0f-4d8e-b635-365d30cf096f</server-id> + <name>Studio</name> + <url>https://studio.atlassian.com</url> + </jira> </servers> </project-configuration> </atlassian-ide-plugin>Index: src/test/com/atlassian/jira/plugin/issueview/TestIssueViewURLHandler.java =================================================================== --- src/test/com/atlassian/jira/plugin/issueview/TestIssueViewURLHandler.java (revision 70395) +++ src/test/com/atlassian/jira/plugin/issueview/TestIssueViewURLHandler.java (working copy) @@ -33,7 +33,7 @@ public void testHandleRequestMalformedURLErrorNoSlashes() throws IOException { - IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null); + IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null, null); Mock mockRequest = new Mock(HttpServletRequest.class); mockRequest.expectAndReturn("getPathInfo", "I'm a dodgy URL (no slashes)"); @@ -49,7 +49,7 @@ public void testHandleRequestMalformedURLErrorOnlyOneSlash() throws IOException { - IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null); + IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null, null); Mock mockRequest = new Mock(HttpServletRequest.class); mockRequest.expectAndReturn("getPathInfo", "/I'm a less dodgy URL (one slash)"); @@ -118,7 +118,7 @@ Mock mockPluginAccessor = new Mock(PluginAccessor.class); mockPluginAccessor.expectAndReturn("getEnabledPluginModule", "jira.issueviews:issue-xml", mockModuleDescriptor); - IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler((PluginAccessor) mockPluginAccessor.proxy(), mockIssueManager, null, null, mockChangeHistoryManager); + IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler((PluginAccessor) mockPluginAccessor.proxy(), mockIssueManager, null, null, mockChangeHistoryManager, null); Mock mockRequest = new Mock(HttpServletRequest.class); mockRequest.expectAndReturn("getPathInfo", "/jira.issueviews:issue-xml/JRA-1/JRA-1.xml"); Index: src/java/com/atlassian/jira/web/bean/CustomViewFieldVisibilityBean.java =================================================================== --- src/java/com/atlassian/jira/web/bean/CustomViewFieldVisibilityBean.java (working copy) +++ src/java/com/atlassian/jira/web/bean/CustomViewFieldVisibilityBean.java (working copy) @@ -0,0 +1,28 @@ +package com.atlassian.jira.web.bean; + + +public class CustomViewFieldVisibilityBean extends FieldVisibilityBean +{ + private CustomViewFieldBean customViewFieldBean; + + public CustomViewFieldVisibilityBean(final CustomViewFieldBean customViewFieldBean) + { + super(); + this.customViewFieldBean = customViewFieldBean; + } + + @Override + public boolean isFieldHidden(Long projectId, String fieldId, String issueTypeId) + { + boolean hidden = true; + if (customViewFieldBean.getFields().contains(fieldId)) + { + if (fieldId.equals(fieldId)) + { + hidden = super.isFieldHidden(projectId, fieldId, issueTypeId); + return hidden; + } + } + return hidden; + } +} Index: src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestURLHandler.java =================================================================== --- src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestURLHandler.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestURLHandler.java (working copy) @@ -20,7 +20,6 @@ import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.jira.web.util.OutlookDate; import com.atlassian.plugin.PluginAccessor; - import org.apache.commons.lang.StringUtils; import java.io.BufferedWriter; @@ -30,7 +29,6 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -53,7 +51,8 @@ */ public static final String NO_HEADERS_PARAMETER = Parameter.NO_HEADERS; - private static final String SAMPLE_URL = "/sr/jira.issueviews:searchrequest-xml/10010/SearchRequest-10010.xml OR /sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml?param1=abc&param2=xyz"; + private static final String SAMPLE_URL + = "/sr/jira.issueviews:searchrequest-xml/10010/SearchRequest-10010.xml OR /sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml?param1=abc&param2=xyz"; private static final String XML_MODULE_NAME = "XML"; private final PluginAccessor pluginAccessor; @@ -68,7 +67,9 @@ // problem with injecting this object is that eventually one of its dependancies is a CustomFieldManager which // eagerly instantiates its custom field cache at object construction. This is bad since the plugin manager has // not yet had a chance to let all plugins register themselves. - public SearchRequestURLHandler(final PluginAccessor pluginAccessor, final JiraAuthenticationContext authenticationContext, final ApplicationProperties applicationProperties, final I18nBean i18nBean, final Authorizer requestAuthorizer, final SearchProvider searchProvider) + public SearchRequestURLHandler(final PluginAccessor pluginAccessor, final JiraAuthenticationContext authenticationContext, + final ApplicationProperties applicationProperties, final I18nBean i18nBean, final Authorizer requestAuthorizer, + final SearchProvider searchProvider) { this.pluginAccessor = pluginAccessor; this.authenticationContext = authenticationContext; @@ -79,7 +80,8 @@ velocityRequestContextFactory = new VelocityRequestContextFactory(applicationProperties); } - public String getURLWithoutContextPath(final SearchRequestViewModuleDescriptor moduleDescriptor, final SearchRequest searchRequest) + public String getURLWithoutContextPath(final SearchRequestViewModuleDescriptor moduleDescriptor, + final SearchRequest searchRequest) { final StringBuffer sb = new StringBuffer(); sb.append("/sr/"); @@ -147,7 +149,8 @@ return; } - final SearchRequestViewModuleDescriptor moduleDescriptor = (SearchRequestViewModuleDescriptor) pluginAccessor.getEnabledPluginModule(pluginKey); + final SearchRequestViewModuleDescriptor moduleDescriptor = (SearchRequestViewModuleDescriptor) pluginAccessor + .getEnabledPluginModule(pluginKey); if (moduleDescriptor == null) { response.sendError(500, "Could not find any enabled plugin with key " + pluginKey); @@ -163,12 +166,13 @@ // temporary search request final Map parameters = request.getParameterMap(); searchRequest = ComponentManager.getInstance().getSearchRequestFactory().getSearchRequestWithSearchSorts(parameters, - authenticationContext.getUser()); + authenticationContext.getUser()); if (searchRequest == null) { response.sendError(500, i18nBean.getText("search.request.invalid")); return; } + view.setRequestParameters(parameters); } else { @@ -204,7 +208,8 @@ { if (view instanceof SearchRequestViewAccessErrorHandler) { - final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler = ((SearchRequestViewAccessErrorHandler) view); + final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler + = ((SearchRequestViewAccessErrorHandler) view); searchRequestViewAccessErrorHandler.writeErrorHeaders(new HttpRequestHeaders(response)); final BufferedWriter writer = new BufferedWriter(response.getWriter()); searchRequestViewAccessErrorHandler.writePermissionViolationError(writer); @@ -230,7 +235,8 @@ //exist, or the user doesn't have permissions to view it. if (view instanceof SearchRequestViewAccessErrorHandler) { - final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler = ((SearchRequestViewAccessErrorHandler) view); + final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler + = ((SearchRequestViewAccessErrorHandler) view); searchRequestViewAccessErrorHandler.writeErrorHeaders(new HttpRequestHeaders(response)); final BufferedWriter writer = new BufferedWriter(response.getWriter()); searchRequestViewAccessErrorHandler.writeSearchRequestDoesNotExistError(writer); @@ -259,13 +265,15 @@ final HashMap searchCountParam = new HashMap(); searchCountParam.put(Parameter.SEARCH_COUNT, String.valueOf(resultCount)); - final SearchRequestParams searchRequestParams = new SearchRequestParamsImpl(request.getSession(true), getPagerFilter(request), - searchCountParam); + final SearchRequestParams searchRequestParams = new SearchRequestParamsImpl(request.getSession(true), + getPagerFilter(request), + searchCountParam); // check that we allow them to run the SearchRequest (ie. its not too big). if (!moduleDescriptor.isExcludeFromLimitFilter()) { - final Result result = requestAuthorizer.isSearchRequestAuthorized(authenticationContext.getUser(), searchRequest, searchRequestParams); + final Result result = requestAuthorizer + .isSearchRequestAuthorized(authenticationContext.getUser(), searchRequest, searchRequestParams); if (!result.isOK()) { response.sendError(403, result.getReason()); @@ -275,11 +283,20 @@ if (!"true".equalsIgnoreCase(request.getParameter(Parameter.NO_HEADERS))) { - response.setContentType(moduleDescriptor.getContentType() + ";charset=" + ManagerFactory.getApplicationProperties().getEncoding()); + response.setContentType( + moduleDescriptor.getContentType() + ";charset=" + ManagerFactory.getApplicationProperties().getEncoding()); view.writeHeaders(searchRequest, new HttpRequestHeaders(response)); } final Writer writer = new BufferedWriter(response.getWriter()); - view.writeSearchResults(searchRequest, searchRequestParams, writer); + try + { + view.writeSearchResults(searchRequest, searchRequestParams, writer); + } + catch (IllegalArgumentException ex) + { + response.sendError(400, ex.getMessage()); + return; + } writer.flush(); } @@ -292,8 +309,9 @@ final StringBuffer sb = new StringBuffer(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\n"); - sb.append("<!-- RSS generated by JIRA ").append(BuildUtils.getVersion()).append(" at ").append(OutlookDate.formatRss(new Date())).append( - " -->\n"); + sb.append("<!-- RSS generated by JIRA ").append(BuildUtils.getVersion()).append(" at ") + .append(OutlookDate.formatRss(new Date())).append( + " -->\n"); sb.append("<rss version=\"0.92\">\n"); sb.append("<channel>\n"); sb.append("\t<title>").append(applicationProperties.getString(APKeys.JIRA_TITLE)).append("</title>\n"); @@ -312,7 +330,8 @@ out.flush(); } - private void loadJsp(final HttpServletRequest request, final HttpServletResponse response, final String jspPage) throws IOException + private void loadJsp(final HttpServletRequest request, final HttpServletResponse response, final String jspPage) + throws IOException { try { @@ -324,7 +343,8 @@ } } - private void redirectToBasicAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws IOException + private void redirectToBasicAuthentication(final HttpServletRequest request, final HttpServletResponse response) + throws IOException { final StringBuffer requestUrl = request.getRequestURL(); if ((requestUrl != null) && StringUtils.isNotEmpty(requestUrl.toString())) Index: src/java/com/atlassian/jira/web/bean/CustomViewFieldBean.java =================================================================== --- src/java/com/atlassian/jira/web/bean/CustomViewFieldBean.java (working copy) +++ src/java/com/atlassian/jira/web/bean/CustomViewFieldBean.java (working copy) @@ -0,0 +1,77 @@ +package com.atlassian.jira.web.bean; + +import java.util.HashSet; +import java.util.Set; + +/** + * TODO: Document this class / interface here + * + * @since v3.13 + */ +public class CustomViewFieldBean +{ + private Set<String> fields; + private Set<String> customFields; + private boolean allCustomFields; + + public CustomViewFieldBean() + { + fields = new HashSet<String>(); + customFields = new HashSet<String>(); + } + + public CustomViewFieldBean(final Set<String> fields, final Set<String> customFields, final boolean allCustomFields) + { + this.fields = fields; + this.customFields = customFields; + this.allCustomFields = allCustomFields; + } + + public Set<String> getFields() + { + return fields; + } + + public void setFields(final Set<String> fields) + { + this.fields = fields; + } + + public void addField(String fieldName) { + if (fields == null) { + fields = new HashSet<String>(); + } + fields.add(fieldName); + } + + public Set<String> getCustomFields() + { + return customFields; + } + + public void setCustomFields(final Set<String> customFields) + { + this.customFields = customFields; + } + + public void addCustomField(String fieldName) { + if (customFields == null) { + customFields = new HashSet<String>(); + } + customFields.add(fieldName); + } + + public boolean isAllCustomFields() + { + return allCustomFields; + } + + public void setAllCustomFields(final boolean allCustomFields) + { + this.allCustomFields = allCustomFields; + } + + public boolean isAnyFieldDefined() { + return allCustomFields || fields.size() > 0 || customFields.size() > 0; + } +} Index: src/java/com/atlassian/jira/issue/views/SearchRequestXMLView.java =================================================================== --- src/java/com/atlassian/jira/issue/views/SearchRequestXMLView.java (revision 70395) +++ src/java/com/atlassian/jira/issue/views/SearchRequestXMLView.java (working copy) @@ -6,9 +6,9 @@ import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.search.SearchException; import com.atlassian.jira.issue.search.SearchRequest; +import com.atlassian.jira.issue.views.util.RssViewUtils; import com.atlassian.jira.issue.views.util.SearchRequestViewBodyWriterUtil; import com.atlassian.jira.issue.views.util.SearchRequestViewUtils; -import com.atlassian.jira.issue.views.util.RssViewUtils; import com.atlassian.jira.plugin.issueview.AbstractIssueView; import com.atlassian.jira.plugin.searchrequestview.AbstractSearchRequestView; import com.atlassian.jira.plugin.searchrequestview.SearchRequestParams; @@ -17,6 +17,8 @@ import com.atlassian.jira.util.JiraVelocityUtils; import com.atlassian.jira.util.velocity.VelocityRequestContext; import com.atlassian.jira.util.velocity.VelocityRequestContextFactory; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; import java.io.IOException; import java.io.Writer; @@ -25,9 +27,6 @@ import java.util.Date; import java.util.Map; -import org.apache.log4j.Logger; -import org.apache.commons.lang.StringUtils; - /** * */ @@ -51,6 +50,7 @@ public void writeSearchResults(SearchRequest searchRequest, SearchRequestParams searchRequestParams, Writer writer) { IssueXMLView xmlView = getIssueXMLView(); + xmlView.setRequestParameters(requestParameters); try { @@ -66,7 +66,7 @@ { public void writeIssue(Issue issue, AbstractIssueView issueView, Writer writer) throws IOException { - if(log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debug("About to write XML view for issue [" + issue.getKey() + "]."); } @@ -145,7 +145,7 @@ { return searchRequestViewBodyWriterUtil.searchCount(searchRequest); } - catch(SearchException se) + catch (SearchException se) { return 0; } Index: src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestView.java =================================================================== --- src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestView.java (working copy) @@ -3,6 +3,7 @@ import com.atlassian.jira.issue.search.SearchRequest; import java.io.Writer; +import java.util.Map; /** * A specific view of a Search Request. Generally this is a different content type (eg. XML, Word or PDF versions @@ -36,4 +37,6 @@ * @param requestHeaders subset of HttpServletResponse responsible for setting headers only */ public void writeHeaders(SearchRequest searchRequest, RequestHeaders requestHeaders); + + public void setRequestParameters(Map requestParameters); } Index: src/java/com/atlassian/jira/plugin/searchrequestview/AbstractSearchRequestView.java =================================================================== --- src/java/com/atlassian/jira/plugin/searchrequestview/AbstractSearchRequestView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/searchrequestview/AbstractSearchRequestView.java (working copy) @@ -4,6 +4,7 @@ import com.atlassian.jira.util.http.JiraHttpUtils; import java.io.Writer; +import java.util.Map; /** * Extendend this abstract class to implement custom SearchRequestViews. By default this @@ -15,6 +16,8 @@ { protected SearchRequestViewModuleDescriptor descriptor; + protected Map requestParameters; + public void init(SearchRequestViewModuleDescriptor moduleDescriptor) { this.descriptor = moduleDescriptor; @@ -31,4 +34,9 @@ } public abstract void writeSearchResults(SearchRequest searchRequest, SearchRequestParams searchRequestParams, Writer writer); + + public void setRequestParameters(final Map requestParameters) + { + this.requestParameters = requestParameters; + } } Index: src/java/com/atlassian/jira/plugin/issueview/IssueViewURLHandler.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/IssueViewURLHandler.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/issueview/IssueViewURLHandler.java (working copy) @@ -6,7 +6,11 @@ import com.atlassian.jira.issue.IssueManager; import com.atlassian.jira.issue.changehistory.ChangeHistoryManager; import com.atlassian.jira.issue.index.DocumentConstants; -import com.atlassian.jira.issue.search.*; +import com.atlassian.jira.issue.search.SearchException; +import com.atlassian.jira.issue.search.SearchParameter; +import com.atlassian.jira.issue.search.SearchProvider; +import com.atlassian.jira.issue.search.SearchRequest; +import com.atlassian.jira.issue.search.SearchResults; import com.atlassian.jira.issue.search.parameters.lucene.StringParameter; import com.atlassian.jira.plugin.searchrequestview.HttpRequestHeaders; import com.atlassian.jira.security.PermissionManager; @@ -18,13 +22,14 @@ import org.apache.commons.lang.exception.NestableRuntimeException; import org.ofbiz.core.entity.GenericEntityException; +import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** - * This class takes care of handling the creation of URLs for the issue view plugin, as well as handling the incoming requests + * This class takes care of handling the creation of URLs for the issue view plugin, as well as handling the incoming + * requests */ public class IssueViewURLHandler { @@ -33,16 +38,18 @@ private final PermissionManager permissionManager; private final SearchProvider searchProvider; private ChangeHistoryManager changeHistoryManager; + private CustomIssueXMLViewHelper customIssueXMLViewHelper; public static final String NO_HEADERS_PARAMETER = "noResponseHeaders"; - public IssueViewURLHandler(PluginAccessor pluginAccessor, IssueManager issueManager, PermissionManager permissionManager, SearchProvider searchProvider, ChangeHistoryManager changeHistoryManager) + public IssueViewURLHandler(PluginAccessor pluginAccessor, IssueManager issueManager, PermissionManager permissionManager, SearchProvider searchProvider, ChangeHistoryManager changeHistoryManager, CustomIssueXMLViewHelper customIssueXMLViewHelper) { this.pluginAccessor = pluginAccessor; this.issueManager = issueManager; this.permissionManager = permissionManager; this.searchProvider = searchProvider; this.changeHistoryManager = changeHistoryManager; + this.customIssueXMLViewHelper = customIssueXMLViewHelper; } public String getURLWithoutContextPath(IssueViewModuleDescriptor moduleDescriptor, String issueKey) @@ -154,8 +161,19 @@ } IssueView view = moduleDescriptor.getIssueView(); - String content = view.getContent(issue); + String content = null; + try + { + view.setRequestParameters(request.getParameterMap()); + content = view.getContent(issue); + } + catch (IllegalArgumentException ex) + { + response.sendError(400, ex.getMessage()); + return; + } + if (!"true".equalsIgnoreCase(request.getParameter(NO_HEADERS_PARAMETER))) { response.setContentType(moduleDescriptor.getContentType() + ";charset=" + ManagerFactory.getApplicationProperties().getEncoding()); @@ -179,10 +197,14 @@ { SearchResults searchResults = searchProvider.search(sr, user, PagerFilter.getUnlimitedFilter()); if (searchResults.getTotal() > 1) + { throw new IllegalStateException("More than one issue returned when searching index for issue key " + issueKey); + } if (searchResults.getTotal() == 0) + { return null; + } return (Issue) searchResults.getIssues().iterator().next(); } Index: src/java/com/atlassian/jira/plugin/issueview/CustomIssueXMLViewHelper.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/CustomIssueXMLViewHelper.java (working copy) +++ src/java/com/atlassian/jira/plugin/issueview/CustomIssueXMLViewHelper.java (working copy) @@ -0,0 +1,108 @@ +package com.atlassian.jira.plugin.issueview; + +import com.atlassian.core.util.map.EasyMap; +import com.atlassian.jira.issue.IssueFieldConstants; +import com.atlassian.jira.issue.fields.CustomField; +import com.atlassian.jira.issue.fields.Field; +import com.atlassian.jira.issue.fields.FieldManager; +import com.atlassian.jira.web.bean.CustomViewFieldBean; +import org.apache.log4j.Logger; + +import java.util.Map; + +/** + * Purpose of this class is to encapsulate XML view field definition parsing. + * If no parameters are defined issue view is backward compatible. + * If parameters are defined and no one is valid class throws exception causing HTTP 400 error. + * + */ +public class CustomIssueXMLViewHelper +{ + private static final Logger log = Logger.getLogger(CustomIssueXMLViewHelper.class); + + /* + Field names mapping. Allows to define mapping between request parameters and names used internally in JIRA. + Mapping adds additional name of internally defined names - both names are valid. + */ + private Map<String, String> fieldNamesMapping = EasyMap.build( + "comments", IssueFieldConstants.COMMENT, + "component", IssueFieldConstants.COMPONENTS); + + /* + Used to retrieve field information. Maybe there is better method? + */ + private FieldManager fieldManager; + + public CustomIssueXMLViewHelper(FieldManager fieldManager) + { + this.fieldManager = fieldManager; + } + + /** + * Method check defined field parameters. If no valid parameter will be found method throws IllegalArgumentException + * to allow http 400 error in IssueViewURLHandler and SearchRequestURLHandler + * + * @param requestParameters HttpServletRequest parameters + * @return CustomView FIeldBean with requested field set or null value if not field parameters were defined + */ + public CustomViewFieldBean getCustomViewFieldBean(Map requestParameters) + { + if (requestParameters != null && requestParameters.containsKey("field")) + { + CustomViewFieldBean customViewFieldBean = new CustomViewFieldBean(); + String[] fieldNames = (String[]) requestParameters.get("field"); + for (String fieldName : fieldNames) + { + if ("allcustom".equals(fieldName)) + { + customViewFieldBean.setAllCustomFields(true); + } + else + { + if (fieldName.startsWith("customfield_")) + { + CustomField customField = null; + try + { + customField = fieldManager.getCustomField(fieldName); + } + catch (IllegalArgumentException ex) + { + log.warn("Invalid field specified for custom issue XML view.", ex); + } + if (customField != null) + { + customViewFieldBean.addCustomField(fieldName); + } + } + else + { + String queryName = fieldName; + if (fieldNamesMapping.containsKey(fieldName)) + { + queryName = fieldNamesMapping.get(fieldName); + } + Field field = fieldManager.getField(queryName); + if (field != null) + { + customViewFieldBean.addField(queryName); + } + else + { + log.warn("Invalid field specified for custom issue XML view: " + fieldName); + } + } + } + } + if (!customViewFieldBean.isAnyFieldDefined()) + { + throw new IllegalArgumentException("No valid field defined for issue XML custom view"); + } + return customViewFieldBean; + } + else + { + return null; + } + } +} Index: src/etc/java/templates/plugins/issueviews/single-xml.vm =================================================================== --- src/etc/java/templates/plugins/issueviews/single-xml.vm (revision 70395) +++ src/etc/java/templates/plugins/issueviews/single-xml.vm (working copy) @@ -9,9 +9,12 @@ #end <item> -<title>[#esc($issue.key)] #esc($issue.summary)</title> -<link>#esc($requestContext.baseUrl)/browse/$issue.key</link> - + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'title', $issue.issueTypeObject.id) == false) + <title>[#esc($issue.key)] #esc($issue.summary)</title> + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'link', $issue.issueTypeObject.id) == false) + <link>#esc($requestContext.baseUrl)/browse/$issue.key</link> + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'description', $issue.issueTypeObject.id) == false) ## RSS Readers expect the body not to be CDATA, so we should not surround with cdata sections <description>#if ($rssMode == 'raw')#if ($issue.description)<![CDATA[#escCdata($issue.description)]]>#end#else#esc($xmlView.getRenderedContent('description', $issue.description, $issue))#end</description> @@ -20,69 +23,78 @@ <environment>#if ($rssMode == 'raw')#if ($issue.environment)<![CDATA[#escCdata($issue.environment)]]>#end#else#esc($xmlView.getRenderedContent('environment', $issue.environment, $issue))#end</environment> #end <key id="$issue.id">#esc($issue.key)</key> + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'summary', $issue.issueTypeObject.id) == false) <summary>#esc($issue.summary)</summary> - - <type id="$issue.issueTypeObject.id" iconUrl="#getNormalizedUrl($issue.issueTypeObject.iconUrl)">#esc($issue.issueTypeObject.nameTranslation)</type> - + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'type', $issue.issueTypeObject.id) == false) + <type id="$issue.issueTypeObject.id" iconUrl="#getNormalizedUrl($issue.issueTypeObject.iconUrl)">#esc($issue.issueTypeObject.nameTranslation)</type> + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'parent', $issue.issueTypeObject.id) == false) #if ($issue.parent) <parent id="$issue.parent.id">$issue.parent.key</parent> #end - + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'priority', $issue.issueTypeObject.id) == false) - #if ($issue.priorityObject)<priority id="$issue.priorityObject.id" iconUrl="#getNormalizedUrl($issue.priorityObject.iconUrl)">#esc($issue.priorityObject.nameTranslation)</priority>#end + #if ($issue.priorityObject) + <priority id="$issue.priorityObject.id" iconUrl="#getNormalizedUrl($issue.priorityObject.iconUrl)">#esc($issue.priorityObject.nameTranslation)</priority> #end - + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'status', $issue.issueTypeObject.id) == false) <status id="$issue.statusObject.id" iconUrl="#getNormalizedUrl($issue.statusObject.iconUrl)">#esc($issue.statusObject.nameTranslation)</status> + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'resolution', $issue.issueTypeObject.id) == false) - #if ($issue.resolutionObject) - <resolution id="$issue.resolutionObject.id">#esc($issue.resolutionObject.nameTranslation)</resolution> - #else - <resolution id="-1">$i18n.getText('common.status.unresolved')</resolution> - #end + #if ($issue.resolutionObject) + <resolution id="$issue.resolutionObject.id">#esc($issue.resolutionObject.nameTranslation)</resolution> + #else + <resolution id="-1">$i18n.getText('common.status.unresolved')</resolution> #end - + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'security', $issue.issueTypeObject.id) == false && $issue.securityLevel) <security id="$issue.securityLevel.id">#esc($issue.securityLevel.name)</security> #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'assignee', $issue.issueTypeObject.id) == false) #if ($issue.assignee) - <assignee username="#esc($issue.assignee.name)">#esc($issue.assignee.fullName)</assignee> + <assignee username="#esc($issue.assignee.name)">#esc($issue.assignee.fullName)</assignee> #else - <assignee username="-1">$i18n.getText('common.concepts.unassigned')</assignee> + <assignee username="-1">$i18n.getText('common.concepts.unassigned')</assignee> #end #end - #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'reporter', $issue.issueTypeObject.id) == false) #if ($issue.reporter) - <reporter username="#esc($issue.reporter.name)">#esc($issue.reporter.fullName)</reporter> + <reporter username="#esc($issue.reporter.name)">#esc($issue.reporter.fullName)</reporter> #else - <reporter username="-1">$i18n.getText('common.words.none')</reporter> + <reporter username="-1">$i18n.getText('common.words.none')</reporter> #end #end - ## there are both 'isCreated' and 'getCreated', so we should hard-code it - <created>$outlookdate.formatRss($issue.getCreated())</created> - <updated>$outlookdate.formatRss($issue.updated)</updated> + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'created', $issue.issueTypeObject.id) == false) + <created>$outlookdate.formatRss($issue.getCreated())</created> + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'updated', $issue.issueTypeObject.id) == false) + <updated>$outlookdate.formatRss($issue.updated)</updated> + #end + + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'resolved', $issue.issueTypeObject.id) == false) #if ($issue.resolutionDate) <resolved>$outlookdate.formatRss($issue.resolutionDate)</resolved> #end - + #end #if ($issue.affectedVersions && ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'versions', $issue.issueTypeObject.id) == false)) #foreach ($version in $issue.affectedVersions) - <version>#esc($version.name)</version> + <version>#esc($version.name)</version> #end #end #if ($issue.fixVersions && ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'fixVersions', $issue.issueTypeObject.id) == false)) #foreach ($version in $issue.fixVersions) - <fixVersion>#esc($version.name)</fixVersion> + <fixVersion>#esc($version.name)</fixVersion> #end #end #if ($issue.components && ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'components', $issue.issueTypeObject.id) == false)) #foreach ($component in $issue.components) - <component>#esc($component.name)</component> + <component>#esc($component.name)</component> #end #end @@ -91,14 +103,22 @@ <due>#if($issue.dueDate)$outlookdate.formatRss($issue.dueDate)#end</due> #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'votes', $issue.issueTypeObject.id) == false) #if ($votingEnabled) <votes>$issue.votes</votes> #end + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'timetracking', $issue.issueTypeObject.id) == false && $timeTrackingEnabled) - #if ($issue.originalEstimate)<timeoriginalestimate seconds="$issue.originalEstimate">$xmlView.getPrettyDuration($issue.originalEstimate)</timeoriginalestimate>#end - #if ($issue.estimate)<timeestimate seconds="$issue.estimate">$xmlView.getPrettyDuration($issue.estimate)</timeestimate>#end - #if ($issue.timeSpent)<timespent seconds="$issue.timeSpent">$xmlView.getPrettyDuration($issue.timeSpent)</timespent>#end + #if ($issue.originalEstimate) + <timeoriginalestimate seconds="$issue.originalEstimate">$xmlView.getPrettyDuration($issue.originalEstimate)</timeoriginalestimate> + #end + #if ($issue.estimate) + <timeestimate seconds="$issue.estimate">$xmlView.getPrettyDuration($issue.estimate)</timeestimate> + #end + #if ($issue.timeSpent) + <timespent seconds="$issue.timeSpent">$xmlView.getPrettyDuration($issue.timeSpent)</timespent> + #end #if ($aggregateTimeTrackingBean) #if ($aggregateTimeTrackingBean.originalEstimate)<aggregatetimeoriginalestimate seconds="$aggregateTimeTrackingBean.originalEstimate">$xmlView.getPrettyDuration($aggregateTimeTrackingBean.originalEstimate)</aggregatetimeoriginalestimate>#end #if ($aggregateTimeTrackingBean.remainingEstimate)<aggregatetimeremainingestimate seconds="$aggregateTimeTrackingBean.remainingEstimate">$xmlView.getPrettyDuration($aggregateTimeTrackingBean.remainingEstimate)</aggregatetimeremainingestimate>#end @@ -115,51 +135,56 @@ </comments> #end -#if ($linkingEnabled && $linkCollection.linkTypes && $linkCollection.linkTypes.isEmpty() == false) - <issuelinks> - #foreach ($issueLinkType in $linkCollection.linkTypes) - <issuelinktype id="#esc($issueLinkType.id.toString())"> - <name>#esc($issueLinkType.name)</name> - #if ($linkCollection.getOutwardIssues($issueLinkType.name)) + #if ($linkingEnabled && $linkCollection.linkTypes && $linkCollection.linkTypes.isEmpty() == false) + <issuelinks> + #foreach ($issueLinkType in $linkCollection.linkTypes) + <issuelinktype id="#esc($issueLinkType.id.toString())"> + <name>#esc($issueLinkType.name)</name> + #if ($linkCollection.getOutwardIssues($issueLinkType.name)) <outwardlinks description="#esc($issueLinkType.outward)"> #printIssueLinks ($linkCollection.getOutwardIssues($issueLinkType.name)) </outwardlinks> - #end - #if ($linkCollection.getInwardIssues($issueLinkType.name)) + #end + #if ($linkCollection.getInwardIssues($issueLinkType.name)) <inwardlinks description="#esc($issueLinkType.inward)"> #printIssueLinks ($linkCollection.getInwardIssues($issueLinkType.name)) </inwardlinks> + #end + </issuelinktype> #end - </issuelinktype> + </issuelinks> + #end + + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'attachments', $issue.issueTypeObject.id) == false) + <attachments> + #foreach ($attachment in $issue.attachments) + <attachment id="$attachment.id" name="#esc($attachment.filename)" size="$attachment.filesize" author="#esc($attachment.author)" created="$outlookdate.formatRss($attachment.created)" /> #end - </issuelinks> -#end - <attachments> - #foreach ($attachment in $issue.attachments) - <attachment id="$attachment.id" name="#esc($attachment.filename)" size="$attachment.filesize" author="#esc($attachment.author)" created="$outlookdate.formatRss($attachment.created)" /> + </attachments> #end - </attachments> - <subtasks> - #foreach ($subtask in $issue.subTaskObjects) - <subtask id="$subtask.id">$subtask.key</subtask> + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'subtasks', $issue.issueTypeObject.id) == false) + <subtasks> + #foreach ($subtask in $issue.subTaskObjects) + <subtask id="$subtask.id">$subtask.key</subtask> + #end + </subtasks> #end - </subtasks> #set ($visibleFields = $xmlView.getVisibleCustomFields($issue, $remoteUser)) #if ($visibleFields && $visibleFields.isEmpty() == false) - <customfields> + <customfields> #foreach ($layoutItem in $xmlView.getVisibleCustomFields($issue, $remoteUser)) #if ($layoutItem.orderableField.hasValue($issue)) - <customfield id="$layoutItem.orderableField.id" key="$layoutItem.orderableField.customFieldType.key"> - <customfieldname>#esc($layoutItem.orderableField.name)</customfieldname> - <customfieldvalues> - $xmlView.getCustomFieldXML($layoutItem.orderableField, $issue) - </customfieldvalues> - </customfield> + <customfield id="$layoutItem.orderableField.id" key="$layoutItem.orderableField.customFieldType.key"> + <customfieldname>#esc($layoutItem.orderableField.name)</customfieldname> + <customfieldvalues> + $xmlView.getCustomFieldXML($layoutItem.orderableField, $issue) + </customfieldvalues> + </customfield> #end #end - </customfields> + </customfields> #end </item> Index: src/java/com/atlassian/jira/plugin/issueview/IssueView.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/IssueView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/issueview/IssueView.java (working copy) @@ -3,6 +3,8 @@ import com.atlassian.jira.issue.Issue; import com.atlassian.jira.plugin.searchrequestview.RequestHeaders; +import java.util.Map; + /** * A specific view of an Issue. Generally this is a different content type (eg. XML, Word or PDF versions * of an issue). @@ -17,4 +19,5 @@ public void writeHeaders(Issue issue, RequestHeaders requestHeaders); + public void setRequestParameters(Map requestParameters); } Index: src/java/com/atlassian/jira/plugin/issueview/AbstractIssueView.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/AbstractIssueView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/issueview/AbstractIssueView.java (working copy) @@ -3,12 +3,17 @@ import com.atlassian.jira.issue.Issue; import com.atlassian.jira.plugin.searchrequestview.RequestHeaders; +import java.util.Map; + /** * */ public abstract class AbstractIssueView implements IssueView { protected IssueViewModuleDescriptor descriptor; + + protected Map requestParameters; + protected static final String ACTION_ORDER_DESC = "desc"; public abstract String getContent(Issue issue); @@ -27,4 +32,9 @@ { // do nothing } + + public void setRequestParameters(final Map requestParameters) + { + this.requestParameters = requestParameters; + } } Index: src/java/com/atlassian/jira/issue/views/IssueXMLView.java =================================================================== --- src/java/com/atlassian/jira/issue/views/IssueXMLView.java (revision 70395) +++ src/java/com/atlassian/jira/issue/views/IssueXMLView.java (working copy) @@ -1,8 +1,10 @@ +import com.atlassian.core.util.collection.EasyList; import com.atlassian.jira.config.properties.APKeys; import com.atlassian.jira.config.properties.ApplicationProperties; import com.atlassian.jira.exception.DataAccessException; import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.IssueFieldConstants; import com.atlassian.jira.issue.comments.CommentManager; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.issue.fields.layout.field.FieldLayout; @@ -16,18 +18,21 @@ import com.atlassian.jira.issue.views.util.RssViewUtils; import com.atlassian.jira.plugin.customfield.CustomFieldTypeModuleDescriptor; import com.atlassian.jira.plugin.issueview.AbstractIssueView; +import com.atlassian.jira.plugin.issueview.CustomIssueXMLViewHelper; import com.atlassian.jira.security.JiraAuthenticationContext; import com.atlassian.jira.util.BuildUtils; -import com.atlassian.core.util.collection.EasyList; import com.atlassian.jira.util.JiraVelocityUtils; import com.atlassian.jira.util.velocity.VelocityRequestContext; import com.atlassian.jira.util.velocity.VelocityRequestContextFactory; +import com.atlassian.jira.web.bean.CustomViewFieldVisibilityBean; import com.atlassian.jira.web.bean.FieldVisibilityBean; +import com.atlassian.jira.web.bean.CustomViewFieldBean; import com.opensymphony.user.User; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; @@ -49,8 +54,12 @@ private final CommentManager commentManager; private final IssueViewUtil issueViewUtil; private final AggregateTimeTrackingCalculatorFactory aggregateTimeTrackingCalculatorFactory; + private CustomIssueXMLViewHelper customIssueXMLViewHelper; - public IssueXMLView(JiraAuthenticationContext authenticationContext, ApplicationProperties applicationProperties, FieldLayoutManager fieldLayoutManager, CommentManager commentManager, IssueViewUtil issueViewUtil, AggregateTimeTrackingCalculatorFactory aggregateTimeTrackingCalculatorFactory) + private CustomViewFieldBean customViewFieldBean; + private FieldVisibilityBean fieldVisibilityBean; + + public IssueXMLView(JiraAuthenticationContext authenticationContext, ApplicationProperties applicationProperties, FieldLayoutManager fieldLayoutManager, CommentManager commentManager, IssueViewUtil issueViewUtil, AggregateTimeTrackingCalculatorFactory aggregateTimeTrackingCalculatorFactory, CustomIssueXMLViewHelper customIssueXMLViewHelper) { this.authenticationContext = authenticationContext; this.applicationProperties = applicationProperties; @@ -58,6 +67,7 @@ this.commentManager = commentManager; this.issueViewUtil = issueViewUtil; this.aggregateTimeTrackingCalculatorFactory = aggregateTimeTrackingCalculatorFactory; + this.customIssueXMLViewHelper = customIssueXMLViewHelper; } public String getContent(Issue issue) @@ -91,44 +101,65 @@ public String getBody(Issue issue) { + Map bodyParams = JiraVelocityUtils.getDefaultVelocityParams(authenticationContext); bodyParams.put("issue", issue); bodyParams.put("i18n", authenticationContext.getI18nBean()); bodyParams.put("outlookdate", authenticationContext.getOutlookDate()); - bodyParams.put("fieldVisibility", new FieldVisibilityBean()); + customViewFieldBean = customIssueXMLViewHelper.getCustomViewFieldBean(requestParameters); + if (customViewFieldBean == null) + { + fieldVisibilityBean = new FieldVisibilityBean(); + } + else { + fieldVisibilityBean = new CustomViewFieldVisibilityBean(customViewFieldBean); + } + bodyParams.put("fieldVisibility", fieldVisibilityBean); + //JRA-13343: If rssMode has been specified in the URL, set it into the velocity parameter map. VelocityRequestContextFactory velocityRequestContextFactory = new VelocityRequestContextFactory(applicationProperties); VelocityRequestContext velocityRequestContext = velocityRequestContextFactory.getJiraVelocityRequestContext(); String rssMode = velocityRequestContext.getRequestParameter("rssMode"); //for the moment lets only allow the 'raw' mode and nothing else - if(StringUtils.isNotEmpty(rssMode) && RSS_MODE_RAW.equals(rssMode)) + if (StringUtils.isNotEmpty(rssMode) && RSS_MODE_RAW.equals(rssMode)) { bodyParams.put("rssMode", RSS_MODE_RAW); } else { - if(StringUtils.isNotEmpty(rssMode)) + if (StringUtils.isNotEmpty(rssMode)) { log.warn("Invalid rssMode parameter specified '" + rssMode + "'. Currently only supports '" + RSS_MODE_RAW + "'"); } bodyParams.put("rssMode", RSS_MODE_RENDERED); } bodyParams.put("votingEnabled", Boolean.valueOf(applicationProperties.getOption(APKeys.JIRA_OPTION_VOTING))); - bodyParams.put("linkingEnabled", Boolean.valueOf(applicationProperties.getOption(APKeys.JIRA_OPTION_ISSUELINKING))); bodyParams.put("xmlView", this); final User user = authenticationContext.getUser(); bodyParams.put("remoteUser", user); - bodyParams.put("linkCollection", issueViewUtil.getLinkCollection(issue, user)); - List comments = commentManager.getCommentsForUser(issue, user); - if (applicationProperties.getDefaultString("jira.issue.actions.order").equals(ACTION_ORDER_DESC)) + + bodyParams.put("linkingEnabled", Boolean.valueOf(applicationProperties.getOption(APKeys.JIRA_OPTION_ISSUELINKING))); + if (!fieldVisibilityBean.isFieldHidden(issue.getProjectObject().getId(), IssueFieldConstants.ISSUE_LINKS, issue.getIssueTypeObject().getId())) { - Collections.reverse(comments); + bodyParams.put("linkCollection", issueViewUtil.getLinkCollection(issue, user)); } - bodyParams.put("comments", comments); + + if (!fieldVisibilityBean.isFieldHidden(issue.getProjectObject().getId(), IssueFieldConstants.COMMENT, issue.getIssueTypeObject().getId())) + { + List comments = commentManager.getCommentsForUser(issue, user); + if (applicationProperties.getDefaultString("jira.issue.actions.order").equals(ACTION_ORDER_DESC)) + { + Collections.reverse(comments); + } + bodyParams.put("comments", comments); + } + final boolean timeTrackingEnabled = applicationProperties.getOption(APKeys.JIRA_OPTION_TIMETRACKING); bodyParams.put("timeTrackingEnabled", Boolean.valueOf(timeTrackingEnabled)); - if (timeTrackingEnabled && !issue.isSubTask()) + if (!fieldVisibilityBean.isFieldHidden(issue.getProjectObject().getId(), IssueFieldConstants.TIMETRACKING, issue.getIssueTypeObject().getId()) + && timeTrackingEnabled && + !issue.isSubTask()) { AggregateTimeTrackingBean bean = aggregateTimeTrackingCalculatorFactory.getCalculator(issue).getAggregates(issue); if (bean.getSubTaskCount() > 0) @@ -155,7 +186,22 @@ { String issueTypeId = issue.getIssueTypeObject().getId(); FieldLayout fieldLayout = fieldLayoutManager.getFieldLayout(issue); - return fieldLayout.getVisibleCustomFieldLayoutItems(user, issue.getProject(), EasyList.build(issueTypeId)); + List<FieldLayoutItem> customFields = fieldLayout.getVisibleCustomFieldLayoutItems(user, issue.getProject(), EasyList.build(issueTypeId)); + if (customViewFieldBean != null) { + if (customViewFieldBean.isAllCustomFields()) { + return customFields; + } else { + List<FieldLayoutItem> requestedCustomFields = new ArrayList<FieldLayoutItem>(); + for (FieldLayoutItem customField : customFields) + { + if (customViewFieldBean.getCustomFields().contains(customField.getOrderableField().getId())) { + requestedCustomFields.add(customField); + } + } + return requestedCustomFields; + } + } + return customFields; } catch (FieldLayoutStorageException e) { Index: src/test/com/atlassian/jira/issue/views/TestIssueXMLView.java =================================================================== --- src/test/com/atlassian/jira/issue/views/TestIssueXMLView.java (revision 70395) +++ src/test/com/atlassian/jira/issue/views/TestIssueXMLView.java (working copy) @@ -55,7 +55,7 @@ ctrlFieldLayoutMgr.setReturnValue(mockFieldLayout.proxy()); ctrlFieldLayoutMgr.replay(); - IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null); + IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null, null); assertEquals("", issueXMLView.getCustomFieldXML(mockCustomField, mockIssue)); } @@ -100,7 +100,7 @@ ctrlFieldLayoutMgr.setReturnValue(mockFieldLayout.proxy()); ctrlFieldLayoutMgr.replay(); - IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null); + IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null, null); assertEquals("", issueXMLView.getCustomFieldXML(mockCustomField, mockIssue)); @@ -149,7 +149,7 @@ ctrlFieldLayoutMgr.setReturnValue(mockFieldLayout.proxy()); ctrlFieldLayoutMgr.replay(); - IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null); + IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null, null); assertEquals(testReturnValue, issueXMLView.getCustomFieldXML(mockCustomField, mockIssue)); Index: src/java/com/atlassian/jira/ComponentManager.java =================================================================== --- src/java/com/atlassian/jira/ComponentManager.java (revision 70395) +++ src/java/com/atlassian/jira/ComponentManager.java (working copy) @@ -365,6 +365,7 @@ import com.atlassian.jira.plugin.assignee.impl.DefaultAssigneeResolver; import com.atlassian.jira.plugin.component.ComponentModuleDescriptor; import com.atlassian.jira.plugin.componentpanel.fragment.impl.ComponentDescriptionFragment; +import com.atlassian.jira.plugin.issueview.CustomIssueXMLViewHelper; import com.atlassian.jira.plugin.issueview.IssueViewURLHandler; import com.atlassian.jira.plugin.profile.DefaultUserFormatManager; import com.atlassian.jira.plugin.profile.DefaultUserFormatMapper; @@ -1537,6 +1538,8 @@ internalContainer.registerComponentImplementation(CreatedVsResolvedChart.class); internalContainer.registerComponentImplementation(ChartUtils.class); + + internalContainer.registerComponentImplementation(CustomIssueXMLViewHelper.class); } /** " is not legal for a JDOM CDATA section: CDATA cannot internally contain a CDATA ending delimiter (]]>). org.jdom.IllegalDataException: The data "Index: atlassian-ide-plugin.xml =================================================================== --- atlassian-ide-plugin.xml (revision 70395) +++ atlassian-ide-plugin.xml (working copy) @@ -55,6 +55,11 @@ <url>https://svn.atlassian.com/atlaseye</url> <isFisheyeInstance>true</isFisheyeInstance> </crucible> + <jira> + <server-id>fcee551d-cd0f-4d8e-b635-365d30cf096f</server-id> + <name>Studio</name> + <url>https://studio.atlassian.com</url> + </jira> </servers> </project-configuration> </atlassian-ide-plugin>Index: src/test/com/atlassian/jira/plugin/issueview/TestIssueViewURLHandler.java =================================================================== --- src/test/com/atlassian/jira/plugin/issueview/TestIssueViewURLHandler.java (revision 70395) +++ src/test/com/atlassian/jira/plugin/issueview/TestIssueViewURLHandler.java (working copy) @@ -33,7 +33,7 @@ public void testHandleRequestMalformedURLErrorNoSlashes() throws IOException { - IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null); + IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null, null); Mock mockRequest = new Mock(HttpServletRequest.class); mockRequest.expectAndReturn("getPathInfo", "I'm a dodgy URL (no slashes)"); @@ -49,7 +49,7 @@ public void testHandleRequestMalformedURLErrorOnlyOneSlash() throws IOException { - IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null); + IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler(null, null, null, null, null, null); Mock mockRequest = new Mock(HttpServletRequest.class); mockRequest.expectAndReturn("getPathInfo", "/I'm a less dodgy URL (one slash)"); @@ -118,7 +118,7 @@ Mock mockPluginAccessor = new Mock(PluginAccessor.class); mockPluginAccessor.expectAndReturn("getEnabledPluginModule", "jira.issueviews:issue-xml", mockModuleDescriptor); - IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler((PluginAccessor) mockPluginAccessor.proxy(), mockIssueManager, null, null, mockChangeHistoryManager); + IssueViewURLHandler issueViewURLHandler = new IssueViewURLHandler((PluginAccessor) mockPluginAccessor.proxy(), mockIssueManager, null, null, mockChangeHistoryManager, null); Mock mockRequest = new Mock(HttpServletRequest.class); mockRequest.expectAndReturn("getPathInfo", "/jira.issueviews:issue-xml/JRA-1/JRA-1.xml"); Index: src/java/com/atlassian/jira/web/bean/CustomViewFieldVisibilityBean.java =================================================================== --- src/java/com/atlassian/jira/web/bean/CustomViewFieldVisibilityBean.java (working copy) +++ src/java/com/atlassian/jira/web/bean/CustomViewFieldVisibilityBean.java (working copy) @@ -0,0 +1,28 @@ +package com.atlassian.jira.web.bean; + + +public class CustomViewFieldVisibilityBean extends FieldVisibilityBean +{ + private CustomViewFieldBean customViewFieldBean; + + public CustomViewFieldVisibilityBean(final CustomViewFieldBean customViewFieldBean) + { + super(); + this.customViewFieldBean = customViewFieldBean; + } + + @Override + public boolean isFieldHidden(Long projectId, String fieldId, String issueTypeId) + { + boolean hidden = true; + if (customViewFieldBean.getFields().contains(fieldId)) + { + if (fieldId.equals(fieldId)) + { + hidden = super.isFieldHidden(projectId, fieldId, issueTypeId); + return hidden; + } + } + return hidden; + } +} Index: src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestURLHandler.java =================================================================== --- src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestURLHandler.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestURLHandler.java (working copy) @@ -20,7 +20,6 @@ import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.jira.web.util.OutlookDate; import com.atlassian.plugin.PluginAccessor; - import org.apache.commons.lang.StringUtils; import java.io.BufferedWriter; @@ -30,7 +29,6 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -53,7 +51,8 @@ */ public static final String NO_HEADERS_PARAMETER = Parameter.NO_HEADERS; - private static final String SAMPLE_URL = "/sr/jira.issueviews:searchrequest-xml/10010/SearchRequest-10010.xml OR /sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml?param1=abc&param2=xyz"; + private static final String SAMPLE_URL + = "/sr/jira.issueviews:searchrequest-xml/10010/SearchRequest-10010.xml OR /sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml?param1=abc&param2=xyz"; private static final String XML_MODULE_NAME = "XML"; private final PluginAccessor pluginAccessor; @@ -68,7 +67,9 @@ // problem with injecting this object is that eventually one of its dependancies is a CustomFieldManager which // eagerly instantiates its custom field cache at object construction. This is bad since the plugin manager has // not yet had a chance to let all plugins register themselves. - public SearchRequestURLHandler(final PluginAccessor pluginAccessor, final JiraAuthenticationContext authenticationContext, final ApplicationProperties applicationProperties, final I18nBean i18nBean, final Authorizer requestAuthorizer, final SearchProvider searchProvider) + public SearchRequestURLHandler(final PluginAccessor pluginAccessor, final JiraAuthenticationContext authenticationContext, + final ApplicationProperties applicationProperties, final I18nBean i18nBean, final Authorizer requestAuthorizer, + final SearchProvider searchProvider) { this.pluginAccessor = pluginAccessor; this.authenticationContext = authenticationContext; @@ -79,7 +80,8 @@ velocityRequestContextFactory = new VelocityRequestContextFactory(applicationProperties); } - public String getURLWithoutContextPath(final SearchRequestViewModuleDescriptor moduleDescriptor, final SearchRequest searchRequest) + public String getURLWithoutContextPath(final SearchRequestViewModuleDescriptor moduleDescriptor, + final SearchRequest searchRequest) { final StringBuffer sb = new StringBuffer(); sb.append("/sr/"); @@ -147,7 +149,8 @@ return; } - final SearchRequestViewModuleDescriptor moduleDescriptor = (SearchRequestViewModuleDescriptor) pluginAccessor.getEnabledPluginModule(pluginKey); + final SearchRequestViewModuleDescriptor moduleDescriptor = (SearchRequestViewModuleDescriptor) pluginAccessor + .getEnabledPluginModule(pluginKey); if (moduleDescriptor == null) { response.sendError(500, "Could not find any enabled plugin with key " + pluginKey); @@ -163,12 +166,13 @@ // temporary search request final Map parameters = request.getParameterMap(); searchRequest = ComponentManager.getInstance().getSearchRequestFactory().getSearchRequestWithSearchSorts(parameters, - authenticationContext.getUser()); + authenticationContext.getUser()); if (searchRequest == null) { response.sendError(500, i18nBean.getText("search.request.invalid")); return; } + view.setRequestParameters(parameters); } else { @@ -204,7 +208,8 @@ { if (view instanceof SearchRequestViewAccessErrorHandler) { - final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler = ((SearchRequestViewAccessErrorHandler) view); + final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler + = ((SearchRequestViewAccessErrorHandler) view); searchRequestViewAccessErrorHandler.writeErrorHeaders(new HttpRequestHeaders(response)); final BufferedWriter writer = new BufferedWriter(response.getWriter()); searchRequestViewAccessErrorHandler.writePermissionViolationError(writer); @@ -230,7 +235,8 @@ //exist, or the user doesn't have permissions to view it. if (view instanceof SearchRequestViewAccessErrorHandler) { - final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler = ((SearchRequestViewAccessErrorHandler) view); + final SearchRequestViewAccessErrorHandler searchRequestViewAccessErrorHandler + = ((SearchRequestViewAccessErrorHandler) view); searchRequestViewAccessErrorHandler.writeErrorHeaders(new HttpRequestHeaders(response)); final BufferedWriter writer = new BufferedWriter(response.getWriter()); searchRequestViewAccessErrorHandler.writeSearchRequestDoesNotExistError(writer); @@ -259,13 +265,15 @@ final HashMap searchCountParam = new HashMap(); searchCountParam.put(Parameter.SEARCH_COUNT, String.valueOf(resultCount)); - final SearchRequestParams searchRequestParams = new SearchRequestParamsImpl(request.getSession(true), getPagerFilter(request), - searchCountParam); + final SearchRequestParams searchRequestParams = new SearchRequestParamsImpl(request.getSession(true), + getPagerFilter(request), + searchCountParam); // check that we allow them to run the SearchRequest (ie. its not too big). if (!moduleDescriptor.isExcludeFromLimitFilter()) { - final Result result = requestAuthorizer.isSearchRequestAuthorized(authenticationContext.getUser(), searchRequest, searchRequestParams); + final Result result = requestAuthorizer + .isSearchRequestAuthorized(authenticationContext.getUser(), searchRequest, searchRequestParams); if (!result.isOK()) { response.sendError(403, result.getReason()); @@ -275,11 +283,20 @@ if (!"true".equalsIgnoreCase(request.getParameter(Parameter.NO_HEADERS))) { - response.setContentType(moduleDescriptor.getContentType() + ";charset=" + ManagerFactory.getApplicationProperties().getEncoding()); + response.setContentType( + moduleDescriptor.getContentType() + ";charset=" + ManagerFactory.getApplicationProperties().getEncoding()); view.writeHeaders(searchRequest, new HttpRequestHeaders(response)); } final Writer writer = new BufferedWriter(response.getWriter()); - view.writeSearchResults(searchRequest, searchRequestParams, writer); + try + { + view.writeSearchResults(searchRequest, searchRequestParams, writer); + } + catch (IllegalArgumentException ex) + { + response.sendError(400, ex.getMessage()); + return; + } writer.flush(); } @@ -292,8 +309,9 @@ final StringBuffer sb = new StringBuffer(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\n"); - sb.append("<!-- RSS generated by JIRA ").append(BuildUtils.getVersion()).append(" at ").append(OutlookDate.formatRss(new Date())).append( - " -->\n"); + sb.append("<!-- RSS generated by JIRA ").append(BuildUtils.getVersion()).append(" at ") + .append(OutlookDate.formatRss(new Date())).append( + " -->\n"); sb.append("<rss version=\"0.92\">\n"); sb.append("<channel>\n"); sb.append("\t<title>").append(applicationProperties.getString(APKeys.JIRA_TITLE)).append("</title>\n"); @@ -312,7 +330,8 @@ out.flush(); } - private void loadJsp(final HttpServletRequest request, final HttpServletResponse response, final String jspPage) throws IOException + private void loadJsp(final HttpServletRequest request, final HttpServletResponse response, final String jspPage) + throws IOException { try { @@ -324,7 +343,8 @@ } } - private void redirectToBasicAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws IOException + private void redirectToBasicAuthentication(final HttpServletRequest request, final HttpServletResponse response) + throws IOException { final StringBuffer requestUrl = request.getRequestURL(); if ((requestUrl != null) && StringUtils.isNotEmpty(requestUrl.toString())) Index: src/java/com/atlassian/jira/web/bean/CustomViewFieldBean.java =================================================================== --- src/java/com/atlassian/jira/web/bean/CustomViewFieldBean.java (working copy) +++ src/java/com/atlassian/jira/web/bean/CustomViewFieldBean.java (working copy) @@ -0,0 +1,77 @@ +package com.atlassian.jira.web.bean; + +import java.util.HashSet; +import java.util.Set; + +/** + * TODO: Document this class / interface here + * + * @since v3.13 + */ +public class CustomViewFieldBean +{ + private Set<String> fields; + private Set<String> customFields; + private boolean allCustomFields; + + public CustomViewFieldBean() + { + fields = new HashSet<String>(); + customFields = new HashSet<String>(); + } + + public CustomViewFieldBean(final Set<String> fields, final Set<String> customFields, final boolean allCustomFields) + { + this.fields = fields; + this.customFields = customFields; + this.allCustomFields = allCustomFields; + } + + public Set<String> getFields() + { + return fields; + } + + public void setFields(final Set<String> fields) + { + this.fields = fields; + } + + public void addField(String fieldName) { + if (fields == null) { + fields = new HashSet<String>(); + } + fields.add(fieldName); + } + + public Set<String> getCustomFields() + { + return customFields; + } + + public void setCustomFields(final Set<String> customFields) + { + this.customFields = customFields; + } + + public void addCustomField(String fieldName) { + if (customFields == null) { + customFields = new HashSet<String>(); + } + customFields.add(fieldName); + } + + public boolean isAllCustomFields() + { + return allCustomFields; + } + + public void setAllCustomFields(final boolean allCustomFields) + { + this.allCustomFields = allCustomFields; + } + + public boolean isAnyFieldDefined() { + return allCustomFields || fields.size() > 0 || customFields.size() > 0; + } +} Index: src/java/com/atlassian/jira/issue/views/SearchRequestXMLView.java =================================================================== --- src/java/com/atlassian/jira/issue/views/SearchRequestXMLView.java (revision 70395) +++ src/java/com/atlassian/jira/issue/views/SearchRequestXMLView.java (working copy) @@ -6,9 +6,9 @@ import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.search.SearchException; import com.atlassian.jira.issue.search.SearchRequest; +import com.atlassian.jira.issue.views.util.RssViewUtils; import com.atlassian.jira.issue.views.util.SearchRequestViewBodyWriterUtil; import com.atlassian.jira.issue.views.util.SearchRequestViewUtils; -import com.atlassian.jira.issue.views.util.RssViewUtils; import com.atlassian.jira.plugin.issueview.AbstractIssueView; import com.atlassian.jira.plugin.searchrequestview.AbstractSearchRequestView; import com.atlassian.jira.plugin.searchrequestview.SearchRequestParams; @@ -17,6 +17,8 @@ import com.atlassian.jira.util.JiraVelocityUtils; import com.atlassian.jira.util.velocity.VelocityRequestContext; import com.atlassian.jira.util.velocity.VelocityRequestContextFactory; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; import java.io.IOException; import java.io.Writer; @@ -25,9 +27,6 @@ import java.util.Date; import java.util.Map; -import org.apache.log4j.Logger; -import org.apache.commons.lang.StringUtils; - /** * */ @@ -51,6 +50,7 @@ public void writeSearchResults(SearchRequest searchRequest, SearchRequestParams searchRequestParams, Writer writer) { IssueXMLView xmlView = getIssueXMLView(); + xmlView.setRequestParameters(requestParameters); try { @@ -66,7 +66,7 @@ { public void writeIssue(Issue issue, AbstractIssueView issueView, Writer writer) throws IOException { - if(log.isDebugEnabled()) + if (log.isDebugEnabled()) { log.debug("About to write XML view for issue [" + issue.getKey() + "]."); } @@ -145,7 +145,7 @@ { return searchRequestViewBodyWriterUtil.searchCount(searchRequest); } - catch(SearchException se) + catch (SearchException se) { return 0; } Index: src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestView.java =================================================================== --- src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/searchrequestview/SearchRequestView.java (working copy) @@ -3,6 +3,7 @@ import com.atlassian.jira.issue.search.SearchRequest; import java.io.Writer; +import java.util.Map; /** * A specific view of a Search Request. Generally this is a different content type (eg. XML, Word or PDF versions @@ -36,4 +37,6 @@ * @param requestHeaders subset of HttpServletResponse responsible for setting headers only */ public void writeHeaders(SearchRequest searchRequest, RequestHeaders requestHeaders); + + public void setRequestParameters(Map requestParameters); } Index: src/java/com/atlassian/jira/plugin/searchrequestview/AbstractSearchRequestView.java =================================================================== --- src/java/com/atlassian/jira/plugin/searchrequestview/AbstractSearchRequestView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/searchrequestview/AbstractSearchRequestView.java (working copy) @@ -4,6 +4,7 @@ import com.atlassian.jira.util.http.JiraHttpUtils; import java.io.Writer; +import java.util.Map; /** * Extendend this abstract class to implement custom SearchRequestViews. By default this @@ -15,6 +16,8 @@ { protected SearchRequestViewModuleDescriptor descriptor; + protected Map requestParameters; + public void init(SearchRequestViewModuleDescriptor moduleDescriptor) { this.descriptor = moduleDescriptor; @@ -31,4 +34,9 @@ } public abstract void writeSearchResults(SearchRequest searchRequest, SearchRequestParams searchRequestParams, Writer writer); + + public void setRequestParameters(final Map requestParameters) + { + this.requestParameters = requestParameters; + } } Index: src/java/com/atlassian/jira/plugin/issueview/IssueViewURLHandler.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/IssueViewURLHandler.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/issueview/IssueViewURLHandler.java (working copy) @@ -6,7 +6,11 @@ import com.atlassian.jira.issue.IssueManager; import com.atlassian.jira.issue.changehistory.ChangeHistoryManager; import com.atlassian.jira.issue.index.DocumentConstants; -import com.atlassian.jira.issue.search.*; +import com.atlassian.jira.issue.search.SearchException; +import com.atlassian.jira.issue.search.SearchParameter; +import com.atlassian.jira.issue.search.SearchProvider; +import com.atlassian.jira.issue.search.SearchRequest; +import com.atlassian.jira.issue.search.SearchResults; import com.atlassian.jira.issue.search.parameters.lucene.StringParameter; import com.atlassian.jira.plugin.searchrequestview.HttpRequestHeaders; import com.atlassian.jira.security.PermissionManager; @@ -18,13 +22,14 @@ import org.apache.commons.lang.exception.NestableRuntimeException; import org.ofbiz.core.entity.GenericEntityException; +import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** - * This class takes care of handling the creation of URLs for the issue view plugin, as well as handling the incoming requests + * This class takes care of handling the creation of URLs for the issue view plugin, as well as handling the incoming + * requests */ public class IssueViewURLHandler { @@ -33,16 +38,18 @@ private final PermissionManager permissionManager; private final SearchProvider searchProvider; private ChangeHistoryManager changeHistoryManager; + private CustomIssueXMLViewHelper customIssueXMLViewHelper; public static final String NO_HEADERS_PARAMETER = "noResponseHeaders"; - public IssueViewURLHandler(PluginAccessor pluginAccessor, IssueManager issueManager, PermissionManager permissionManager, SearchProvider searchProvider, ChangeHistoryManager changeHistoryManager) + public IssueViewURLHandler(PluginAccessor pluginAccessor, IssueManager issueManager, PermissionManager permissionManager, SearchProvider searchProvider, ChangeHistoryManager changeHistoryManager, CustomIssueXMLViewHelper customIssueXMLViewHelper) { this.pluginAccessor = pluginAccessor; this.issueManager = issueManager; this.permissionManager = permissionManager; this.searchProvider = searchProvider; this.changeHistoryManager = changeHistoryManager; + this.customIssueXMLViewHelper = customIssueXMLViewHelper; } public String getURLWithoutContextPath(IssueViewModuleDescriptor moduleDescriptor, String issueKey) @@ -154,8 +161,19 @@ } IssueView view = moduleDescriptor.getIssueView(); - String content = view.getContent(issue); + String content = null; + try + { + view.setRequestParameters(request.getParameterMap()); + content = view.getContent(issue); + } + catch (IllegalArgumentException ex) + { + response.sendError(400, ex.getMessage()); + return; + } + if (!"true".equalsIgnoreCase(request.getParameter(NO_HEADERS_PARAMETER))) { response.setContentType(moduleDescriptor.getContentType() + ";charset=" + ManagerFactory.getApplicationProperties().getEncoding()); @@ -179,10 +197,14 @@ { SearchResults searchResults = searchProvider.search(sr, user, PagerFilter.getUnlimitedFilter()); if (searchResults.getTotal() > 1) + { throw new IllegalStateException("More than one issue returned when searching index for issue key " + issueKey); + } if (searchResults.getTotal() == 0) + { return null; + } return (Issue) searchResults.getIssues().iterator().next(); } Index: src/java/com/atlassian/jira/plugin/issueview/CustomIssueXMLViewHelper.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/CustomIssueXMLViewHelper.java (working copy) +++ src/java/com/atlassian/jira/plugin/issueview/CustomIssueXMLViewHelper.java (working copy) @@ -0,0 +1,108 @@ +package com.atlassian.jira.plugin.issueview; + +import com.atlassian.core.util.map.EasyMap; +import com.atlassian.jira.issue.IssueFieldConstants; +import com.atlassian.jira.issue.fields.CustomField; +import com.atlassian.jira.issue.fields.Field; +import com.atlassian.jira.issue.fields.FieldManager; +import com.atlassian.jira.web.bean.CustomViewFieldBean; +import org.apache.log4j.Logger; + +import java.util.Map; + +/** + * Purpose of this class is to encapsulate XML view field definition parsing. + * If no parameters are defined issue view is backward compatible. + * If parameters are defined and no one is valid class throws exception causing HTTP 400 error. + * + */ +public class CustomIssueXMLViewHelper +{ + private static final Logger log = Logger.getLogger(CustomIssueXMLViewHelper.class); + + /* + Field names mapping. Allows to define mapping between request parameters and names used internally in JIRA. + Mapping adds additional name of internally defined names - both names are valid. + */ + private Map<String, String> fieldNamesMapping = EasyMap.build( + "comments", IssueFieldConstants.COMMENT, + "component", IssueFieldConstants.COMPONENTS); + + /* + Used to retrieve field information. Maybe there is better method? + */ + private FieldManager fieldManager; + + public CustomIssueXMLViewHelper(FieldManager fieldManager) + { + this.fieldManager = fieldManager; + } + + /** + * Method check defined field parameters. If no valid parameter will be found method throws IllegalArgumentException + * to allow http 400 error in IssueViewURLHandler and SearchRequestURLHandler + * + * @param requestParameters HttpServletRequest parameters + * @return CustomView FIeldBean with requested field set or null value if not field parameters were defined + */ + public CustomViewFieldBean getCustomViewFieldBean(Map requestParameters) + { + if (requestParameters != null && requestParameters.containsKey("field")) + { + CustomViewFieldBean customViewFieldBean = new CustomViewFieldBean(); + String[] fieldNames = (String[]) requestParameters.get("field"); + for (String fieldName : fieldNames) + { + if ("allcustom".equals(fieldName)) + { + customViewFieldBean.setAllCustomFields(true); + } + else + { + if (fieldName.startsWith("customfield_")) + { + CustomField customField = null; + try + { + customField = fieldManager.getCustomField(fieldName); + } + catch (IllegalArgumentException ex) + { + log.warn("Invalid field specified for custom issue XML view.", ex); + } + if (customField != null) + { + customViewFieldBean.addCustomField(fieldName); + } + } + else + { + String queryName = fieldName; + if (fieldNamesMapping.containsKey(fieldName)) + { + queryName = fieldNamesMapping.get(fieldName); + } + Field field = fieldManager.getField(queryName); + if (field != null) + { + customViewFieldBean.addField(queryName); + } + else + { + log.warn("Invalid field specified for custom issue XML view: " + fieldName); + } + } + } + } + if (!customViewFieldBean.isAnyFieldDefined()) + { + throw new IllegalArgumentException("No valid field defined for issue XML custom view"); + } + return customViewFieldBean; + } + else + { + return null; + } + } +} Index: src/etc/java/templates/plugins/issueviews/single-xml.vm =================================================================== --- src/etc/java/templates/plugins/issueviews/single-xml.vm (revision 70395) +++ src/etc/java/templates/plugins/issueviews/single-xml.vm (working copy) @@ -9,9 +9,12 @@ #end <item> -<title>[#esc($issue.key)] #esc($issue.summary)</title> -<link>#esc($requestContext.baseUrl)/browse/$issue.key</link> - + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'title', $issue.issueTypeObject.id) == false) + <title>[#esc($issue.key)] #esc($issue.summary)</title> + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'link', $issue.issueTypeObject.id) == false) + <link>#esc($requestContext.baseUrl)/browse/$issue.key</link> + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'description', $issue.issueTypeObject.id) == false) ## RSS Readers expect the body not to be CDATA, so we should not surround with cdata sections <description>#if ($rssMode == 'raw')#if ($issue.description)<![CDATA[#escCdata($issue.description)]]>#end#else#esc($xmlView.getRenderedContent('description', $issue.description, $issue))#end</description> @@ -20,69 +23,78 @@ <environment>#if ($rssMode == 'raw')#if ($issue.environment)<![CDATA[#escCdata($issue.environment)]]>#end#else#esc($xmlView.getRenderedContent('environment', $issue.environment, $issue))#end</environment> #end <key id="$issue.id">#esc($issue.key)</key> + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'summary', $issue.issueTypeObject.id) == false) <summary>#esc($issue.summary)</summary> - - <type id="$issue.issueTypeObject.id" iconUrl="#getNormalizedUrl($issue.issueTypeObject.iconUrl)">#esc($issue.issueTypeObject.nameTranslation)</type> - + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'type', $issue.issueTypeObject.id) == false) + <type id="$issue.issueTypeObject.id" iconUrl="#getNormalizedUrl($issue.issueTypeObject.iconUrl)">#esc($issue.issueTypeObject.nameTranslation)</type> + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'parent', $issue.issueTypeObject.id) == false) #if ($issue.parent) <parent id="$issue.parent.id">$issue.parent.key</parent> #end - + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'priority', $issue.issueTypeObject.id) == false) - #if ($issue.priorityObject)<priority id="$issue.priorityObject.id" iconUrl="#getNormalizedUrl($issue.priorityObject.iconUrl)">#esc($issue.priorityObject.nameTranslation)</priority>#end + #if ($issue.priorityObject) + <priority id="$issue.priorityObject.id" iconUrl="#getNormalizedUrl($issue.priorityObject.iconUrl)">#esc($issue.priorityObject.nameTranslation)</priority> #end - + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'status', $issue.issueTypeObject.id) == false) <status id="$issue.statusObject.id" iconUrl="#getNormalizedUrl($issue.statusObject.iconUrl)">#esc($issue.statusObject.nameTranslation)</status> + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'resolution', $issue.issueTypeObject.id) == false) - #if ($issue.resolutionObject) - <resolution id="$issue.resolutionObject.id">#esc($issue.resolutionObject.nameTranslation)</resolution> - #else - <resolution id="-1">$i18n.getText('common.status.unresolved')</resolution> - #end + #if ($issue.resolutionObject) + <resolution id="$issue.resolutionObject.id">#esc($issue.resolutionObject.nameTranslation)</resolution> + #else + <resolution id="-1">$i18n.getText('common.status.unresolved')</resolution> #end - + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'security', $issue.issueTypeObject.id) == false && $issue.securityLevel) <security id="$issue.securityLevel.id">#esc($issue.securityLevel.name)</security> #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'assignee', $issue.issueTypeObject.id) == false) #if ($issue.assignee) - <assignee username="#esc($issue.assignee.name)">#esc($issue.assignee.fullName)</assignee> + <assignee username="#esc($issue.assignee.name)">#esc($issue.assignee.fullName)</assignee> #else - <assignee username="-1">$i18n.getText('common.concepts.unassigned')</assignee> + <assignee username="-1">$i18n.getText('common.concepts.unassigned')</assignee> #end #end - #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'reporter', $issue.issueTypeObject.id) == false) #if ($issue.reporter) - <reporter username="#esc($issue.reporter.name)">#esc($issue.reporter.fullName)</reporter> + <reporter username="#esc($issue.reporter.name)">#esc($issue.reporter.fullName)</reporter> #else - <reporter username="-1">$i18n.getText('common.words.none')</reporter> + <reporter username="-1">$i18n.getText('common.words.none')</reporter> #end #end - ## there are both 'isCreated' and 'getCreated', so we should hard-code it - <created>$outlookdate.formatRss($issue.getCreated())</created> - <updated>$outlookdate.formatRss($issue.updated)</updated> + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'created', $issue.issueTypeObject.id) == false) + <created>$outlookdate.formatRss($issue.getCreated())</created> + #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'updated', $issue.issueTypeObject.id) == false) + <updated>$outlookdate.formatRss($issue.updated)</updated> + #end + + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'resolved', $issue.issueTypeObject.id) == false) #if ($issue.resolutionDate) <resolved>$outlookdate.formatRss($issue.resolutionDate)</resolved> #end - + #end #if ($issue.affectedVersions && ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'versions', $issue.issueTypeObject.id) == false)) #foreach ($version in $issue.affectedVersions) - <version>#esc($version.name)</version> + <version>#esc($version.name)</version> #end #end #if ($issue.fixVersions && ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'fixVersions', $issue.issueTypeObject.id) == false)) #foreach ($version in $issue.fixVersions) - <fixVersion>#esc($version.name)</fixVersion> + <fixVersion>#esc($version.name)</fixVersion> #end #end #if ($issue.components && ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'components', $issue.issueTypeObject.id) == false)) #foreach ($component in $issue.components) - <component>#esc($component.name)</component> + <component>#esc($component.name)</component> #end #end @@ -91,14 +103,22 @@ <due>#if($issue.dueDate)$outlookdate.formatRss($issue.dueDate)#end</due> #end + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'votes', $issue.issueTypeObject.id) == false) #if ($votingEnabled) <votes>$issue.votes</votes> #end + #end #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'timetracking', $issue.issueTypeObject.id) == false && $timeTrackingEnabled) - #if ($issue.originalEstimate)<timeoriginalestimate seconds="$issue.originalEstimate">$xmlView.getPrettyDuration($issue.originalEstimate)</timeoriginalestimate>#end - #if ($issue.estimate)<timeestimate seconds="$issue.estimate">$xmlView.getPrettyDuration($issue.estimate)</timeestimate>#end - #if ($issue.timeSpent)<timespent seconds="$issue.timeSpent">$xmlView.getPrettyDuration($issue.timeSpent)</timespent>#end + #if ($issue.originalEstimate) + <timeoriginalestimate seconds="$issue.originalEstimate">$xmlView.getPrettyDuration($issue.originalEstimate)</timeoriginalestimate> + #end + #if ($issue.estimate) + <timeestimate seconds="$issue.estimate">$xmlView.getPrettyDuration($issue.estimate)</timeestimate> + #end + #if ($issue.timeSpent) + <timespent seconds="$issue.timeSpent">$xmlView.getPrettyDuration($issue.timeSpent)</timespent> + #end #if ($aggregateTimeTrackingBean) #if ($aggregateTimeTrackingBean.originalEstimate)<aggregatetimeoriginalestimate seconds="$aggregateTimeTrackingBean.originalEstimate">$xmlView.getPrettyDuration($aggregateTimeTrackingBean.originalEstimate)</aggregatetimeoriginalestimate>#end #if ($aggregateTimeTrackingBean.remainingEstimate)<aggregatetimeremainingestimate seconds="$aggregateTimeTrackingBean.remainingEstimate">$xmlView.getPrettyDuration($aggregateTimeTrackingBean.remainingEstimate)</aggregatetimeremainingestimate>#end @@ -115,51 +135,56 @@ </comments> #end -#if ($linkingEnabled && $linkCollection.linkTypes && $linkCollection.linkTypes.isEmpty() == false) - <issuelinks> - #foreach ($issueLinkType in $linkCollection.linkTypes) - <issuelinktype id="#esc($issueLinkType.id.toString())"> - <name>#esc($issueLinkType.name)</name> - #if ($linkCollection.getOutwardIssues($issueLinkType.name)) + #if ($linkingEnabled && $linkCollection.linkTypes && $linkCollection.linkTypes.isEmpty() == false) + <issuelinks> + #foreach ($issueLinkType in $linkCollection.linkTypes) + <issuelinktype id="#esc($issueLinkType.id.toString())"> + <name>#esc($issueLinkType.name)</name> + #if ($linkCollection.getOutwardIssues($issueLinkType.name)) <outwardlinks description="#esc($issueLinkType.outward)"> #printIssueLinks ($linkCollection.getOutwardIssues($issueLinkType.name)) </outwardlinks> - #end - #if ($linkCollection.getInwardIssues($issueLinkType.name)) + #end + #if ($linkCollection.getInwardIssues($issueLinkType.name)) <inwardlinks description="#esc($issueLinkType.inward)"> #printIssueLinks ($linkCollection.getInwardIssues($issueLinkType.name)) </inwardlinks> + #end + </issuelinktype> #end - </issuelinktype> + </issuelinks> + #end + + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'attachments', $issue.issueTypeObject.id) == false) + <attachments> + #foreach ($attachment in $issue.attachments) + <attachment id="$attachment.id" name="#esc($attachment.filename)" size="$attachment.filesize" author="#esc($attachment.author)" created="$outlookdate.formatRss($attachment.created)" /> #end - </issuelinks> -#end - <attachments> - #foreach ($attachment in $issue.attachments) - <attachment id="$attachment.id" name="#esc($attachment.filename)" size="$attachment.filesize" author="#esc($attachment.author)" created="$outlookdate.formatRss($attachment.created)" /> + </attachments> #end - </attachments> - <subtasks> - #foreach ($subtask in $issue.subTaskObjects) - <subtask id="$subtask.id">$subtask.key</subtask> + #if ($fieldVisibility.isFieldHidden($issue.project.getLong('id'), 'subtasks', $issue.issueTypeObject.id) == false) + <subtasks> + #foreach ($subtask in $issue.subTaskObjects) + <subtask id="$subtask.id">$subtask.key</subtask> + #end + </subtasks> #end - </subtasks> #set ($visibleFields = $xmlView.getVisibleCustomFields($issue, $remoteUser)) #if ($visibleFields && $visibleFields.isEmpty() == false) - <customfields> + <customfields> #foreach ($layoutItem in $xmlView.getVisibleCustomFields($issue, $remoteUser)) #if ($layoutItem.orderableField.hasValue($issue)) - <customfield id="$layoutItem.orderableField.id" key="$layoutItem.orderableField.customFieldType.key"> - <customfieldname>#esc($layoutItem.orderableField.name)</customfieldname> - <customfieldvalues> - $xmlView.getCustomFieldXML($layoutItem.orderableField, $issue) - </customfieldvalues> - </customfield> + <customfield id="$layoutItem.orderableField.id" key="$layoutItem.orderableField.customFieldType.key"> + <customfieldname>#esc($layoutItem.orderableField.name)</customfieldname> + <customfieldvalues> + $xmlView.getCustomFieldXML($layoutItem.orderableField, $issue) + </customfieldvalues> + </customfield> #end #end - </customfields> + </customfields> #end </item> Index: src/java/com/atlassian/jira/plugin/issueview/IssueView.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/IssueView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/issueview/IssueView.java (working copy) @@ -3,6 +3,8 @@ import com.atlassian.jira.issue.Issue; import com.atlassian.jira.plugin.searchrequestview.RequestHeaders; +import java.util.Map; + /** * A specific view of an Issue. Generally this is a different content type (eg. XML, Word or PDF versions * of an issue). @@ -17,4 +19,5 @@ public void writeHeaders(Issue issue, RequestHeaders requestHeaders); + public void setRequestParameters(Map requestParameters); } Index: src/java/com/atlassian/jira/plugin/issueview/AbstractIssueView.java =================================================================== --- src/java/com/atlassian/jira/plugin/issueview/AbstractIssueView.java (revision 70395) +++ src/java/com/atlassian/jira/plugin/issueview/AbstractIssueView.java (working copy) @@ -3,12 +3,17 @@ import com.atlassian.jira.issue.Issue; import com.atlassian.jira.plugin.searchrequestview.RequestHeaders; +import java.util.Map; + /** * */ public abstract class AbstractIssueView implements IssueView { protected IssueViewModuleDescriptor descriptor; + + protected Map requestParameters; + protected static final String ACTION_ORDER_DESC = "desc"; public abstract String getContent(Issue issue); @@ -27,4 +32,9 @@ { // do nothing } + + public void setRequestParameters(final Map requestParameters) + { + this.requestParameters = requestParameters; + } } Index: src/java/com/atlassian/jira/issue/views/IssueXMLView.java =================================================================== --- src/java/com/atlassian/jira/issue/views/IssueXMLView.java (revision 70395) +++ src/java/com/atlassian/jira/issue/views/IssueXMLView.java (working copy) @@ -1,8 +1,10 @@ +import com.atlassian.core.util.collection.EasyList; import com.atlassian.jira.config.properties.APKeys; import com.atlassian.jira.config.properties.ApplicationProperties; import com.atlassian.jira.exception.DataAccessException; import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.issue.IssueFieldConstants; import com.atlassian.jira.issue.comments.CommentManager; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.issue.fields.layout.field.FieldLayout; @@ -16,18 +18,21 @@ import com.atlassian.jira.issue.views.util.RssViewUtils; import com.atlassian.jira.plugin.customfield.CustomFieldTypeModuleDescriptor; import com.atlassian.jira.plugin.issueview.AbstractIssueView; +import com.atlassian.jira.plugin.issueview.CustomIssueXMLViewHelper; import com.atlassian.jira.security.JiraAuthenticationContext; import com.atlassian.jira.util.BuildUtils; -import com.atlassian.core.util.collection.EasyList; import com.atlassian.jira.util.JiraVelocityUtils; import com.atlassian.jira.util.velocity.VelocityRequestContext; import com.atlassian.jira.util.velocity.VelocityRequestContextFactory; +import com.atlassian.jira.web.bean.CustomViewFieldVisibilityBean; import com.atlassian.jira.web.bean.FieldVisibilityBean; +import com.atlassian.jira.web.bean.CustomViewFieldBean; import com.opensymphony.user.User; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; @@ -49,8 +54,12 @@ private final CommentManager commentManager; private final IssueViewUtil issueViewUtil; private final AggregateTimeTrackingCalculatorFactory aggregateTimeTrackingCalculatorFactory; + private CustomIssueXMLViewHelper customIssueXMLViewHelper; - public IssueXMLView(JiraAuthenticationContext authenticationContext, ApplicationProperties applicationProperties, FieldLayoutManager fieldLayoutManager, CommentManager commentManager, IssueViewUtil issueViewUtil, AggregateTimeTrackingCalculatorFactory aggregateTimeTrackingCalculatorFactory) + private CustomViewFieldBean customViewFieldBean; + private FieldVisibilityBean fieldVisibilityBean; + + public IssueXMLView(JiraAuthenticationContext authenticationContext, ApplicationProperties applicationProperties, FieldLayoutManager fieldLayoutManager, CommentManager commentManager, IssueViewUtil issueViewUtil, AggregateTimeTrackingCalculatorFactory aggregateTimeTrackingCalculatorFactory, CustomIssueXMLViewHelper customIssueXMLViewHelper) { this.authenticationContext = authenticationContext; this.applicationProperties = applicationProperties; @@ -58,6 +67,7 @@ this.commentManager = commentManager; this.issueViewUtil = issueViewUtil; this.aggregateTimeTrackingCalculatorFactory = aggregateTimeTrackingCalculatorFactory; + this.customIssueXMLViewHelper = customIssueXMLViewHelper; } public String getContent(Issue issue) @@ -91,44 +101,65 @@ public String getBody(Issue issue) { + Map bodyParams = JiraVelocityUtils.getDefaultVelocityParams(authenticationContext); bodyParams.put("issue", issue); bodyParams.put("i18n", authenticationContext.getI18nBean()); bodyParams.put("outlookdate", authenticationContext.getOutlookDate()); - bodyParams.put("fieldVisibility", new FieldVisibilityBean()); + customViewFieldBean = customIssueXMLViewHelper.getCustomViewFieldBean(requestParameters); + if (customViewFieldBean == null) + { + fieldVisibilityBean = new FieldVisibilityBean(); + } + else { + fieldVisibilityBean = new CustomViewFieldVisibilityBean(customViewFieldBean); + } + bodyParams.put("fieldVisibility", fieldVisibilityBean); + //JRA-13343: If rssMode has been specified in the URL, set it into the velocity parameter map. VelocityRequestContextFactory velocityRequestContextFactory = new VelocityRequestContextFactory(applicationProperties); VelocityRequestContext velocityRequestContext = velocityRequestContextFactory.getJiraVelocityRequestContext(); String rssMode = velocityRequestContext.getRequestParameter("rssMode"); //for the moment lets only allow the 'raw' mode and nothing else - if(StringUtils.isNotEmpty(rssMode) && RSS_MODE_RAW.equals(rssMode)) + if (StringUtils.isNotEmpty(rssMode) && RSS_MODE_RAW.equals(rssMode)) { bodyParams.put("rssMode", RSS_MODE_RAW); } else { - if(StringUtils.isNotEmpty(rssMode)) + if (StringUtils.isNotEmpty(rssMode)) { log.warn("Invalid rssMode parameter specified '" + rssMode + "'. Currently only supports '" + RSS_MODE_RAW + "'"); } bodyParams.put("rssMode", RSS_MODE_RENDERED); } bodyParams.put("votingEnabled", Boolean.valueOf(applicationProperties.getOption(APKeys.JIRA_OPTION_VOTING))); - bodyParams.put("linkingEnabled", Boolean.valueOf(applicationProperties.getOption(APKeys.JIRA_OPTION_ISSUELINKING))); bodyParams.put("xmlView", this); final User user = authenticationContext.getUser(); bodyParams.put("remoteUser", user); - bodyParams.put("linkCollection", issueViewUtil.getLinkCollection(issue, user)); - List comments = commentManager.getCommentsForUser(issue, user); - if (applicationProperties.getDefaultString("jira.issue.actions.order").equals(ACTION_ORDER_DESC)) + + bodyParams.put("linkingEnabled", Boolean.valueOf(applicationProperties.getOption(APKeys.JIRA_OPTION_ISSUELINKING))); + if (!fieldVisibilityBean.isFieldHidden(issue.getProjectObject().getId(), IssueFieldConstants.ISSUE_LINKS, issue.getIssueTypeObject().getId())) { - Collections.reverse(comments); + bodyParams.put("linkCollection", issueViewUtil.getLinkCollection(issue, user)); } - bodyParams.put("comments", comments); + + if (!fieldVisibilityBean.isFieldHidden(issue.getProjectObject().getId(), IssueFieldConstants.COMMENT, issue.getIssueTypeObject().getId())) + { + List comments = commentManager.getCommentsForUser(issue, user); + if (applicationProperties.getDefaultString("jira.issue.actions.order").equals(ACTION_ORDER_DESC)) + { + Collections.reverse(comments); + } + bodyParams.put("comments", comments); + } + final boolean timeTrackingEnabled = applicationProperties.getOption(APKeys.JIRA_OPTION_TIMETRACKING); bodyParams.put("timeTrackingEnabled", Boolean.valueOf(timeTrackingEnabled)); - if (timeTrackingEnabled && !issue.isSubTask()) + if (!fieldVisibilityBean.isFieldHidden(issue.getProjectObject().getId(), IssueFieldConstants.TIMETRACKING, issue.getIssueTypeObject().getId()) + && timeTrackingEnabled && + !issue.isSubTask()) { AggregateTimeTrackingBean bean = aggregateTimeTrackingCalculatorFactory.getCalculator(issue).getAggregates(issue); if (bean.getSubTaskCount() > 0) @@ -155,7 +186,22 @@ { String issueTypeId = issue.getIssueTypeObject().getId(); FieldLayout fieldLayout = fieldLayoutManager.getFieldLayout(issue); - return fieldLayout.getVisibleCustomFieldLayoutItems(user, issue.getProject(), EasyList.build(issueTypeId)); + List<FieldLayoutItem> customFields = fieldLayout.getVisibleCustomFieldLayoutItems(user, issue.getProject(), EasyList.build(issueTypeId)); + if (customViewFieldBean != null) { + if (customViewFieldBean.isAllCustomFields()) { + return customFields; + } else { + List<FieldLayoutItem> requestedCustomFields = new ArrayList<FieldLayoutItem>(); + for (FieldLayoutItem customField : customFields) + { + if (customViewFieldBean.getCustomFields().contains(customField.getOrderableField().getId())) { + requestedCustomFields.add(customField); + } + } + return requestedCustomFields; + } + } + return customFields; } catch (FieldLayoutStorageException e) { Index: src/test/com/atlassian/jira/issue/views/TestIssueXMLView.java =================================================================== --- src/test/com/atlassian/jira/issue/views/TestIssueXMLView.java (revision 70395) +++ src/test/com/atlassian/jira/issue/views/TestIssueXMLView.java (working copy) @@ -55,7 +55,7 @@ ctrlFieldLayoutMgr.setReturnValue(mockFieldLayout.proxy()); ctrlFieldLayoutMgr.replay(); - IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null); + IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null, null); assertEquals("", issueXMLView.getCustomFieldXML(mockCustomField, mockIssue)); } @@ -100,7 +100,7 @@ ctrlFieldLayoutMgr.setReturnValue(mockFieldLayout.proxy()); ctrlFieldLayoutMgr.replay(); - IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null); + IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null, null); assertEquals("", issueXMLView.getCustomFieldXML(mockCustomField, mockIssue)); @@ -149,7 +149,7 @@ ctrlFieldLayoutMgr.setReturnValue(mockFieldLayout.proxy()); ctrlFieldLayoutMgr.replay(); - IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null); + IssueXMLView issueXMLView = new IssueXMLView(null, null, mockFieldLayoutManager, null, null, null, null); assertEquals(testReturnValue, issueXMLView.getCustomFieldXML(mockCustomField, mockIssue)); Index: src/java/com/atlassian/jira/ComponentManager.java =================================================================== --- src/java/com/atlassian/jira/ComponentManager.java (revision 70395) +++ src/java/com/atlassian/jira/ComponentManager.java (working copy) @@ -365,6 +365,7 @@ import com.atlassian.jira.plugin.assignee.impl.DefaultAssigneeResolver; import com.atlassian.jira.plugin.component.ComponentModuleDescriptor; import com.atlassian.jira.plugin.componentpanel.fragment.impl.ComponentDescriptionFragment; +import com.atlassian.jira.plugin.issueview.CustomIssueXMLViewHelper; import com.atlassian.jira.plugin.issueview.IssueViewURLHandler; import com.atlassian.jira.plugin.profile.DefaultUserFormatManager; import com.atlassian.jira.plugin.profile.DefaultUserFormatMapper; @@ -1537,6 +1538,8 @@ internalContainer.registerComponentImplementation(CreatedVsResolvedChart.class); internalContainer.registerComponentImplementation(ChartUtils.class); + + internalContainer.registerComponentImplementation(CustomIssueXMLViewHelper.class); } /** " is not legal for a JDOM CDATA section: CDATA cannot internally contain a CDATA ending delimiter (]]>). at org.jdom.CDATA.setText(CDATA.java:121) at org.jdom.CDATA.<init>(CDATA.java:96) at com.atlassian.theplugin.commons.crucible.api.rest.CrucibleRestXmlHelper.prepareCreateReviewNode(CrucibleRestXmlHelper.java:262) at com.atlassian.theplugin.commons.crucible.api.rest.CrucibleSessionImpl.createReviewFromPatch(CrucibleSessionImpl.java:1041) at com.atlassian.theplugin.commons.crucible.CrucibleServerFacadeImpl.createReviewFromPatch(CrucibleServerFacadeImpl.java:278) at com.atlassian.theplugin.idea.crucible.CrucibleReviewCreateForm.doOKAction(CrucibleReviewCreateForm.java:806) at com.intellij.openapi.ui.DialogWrapper$OkAction.actionPerformed(DialogWrapper.java:946) at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1882) at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2202) at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:420) at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:258) at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236) at java.awt.Component.processMouseEvent(Component.java:5602) at javax.swing.JComponent.processMouseEvent(JComponent.java:3135) at java.awt.Component.processEvent(Component.java:5367) at java.awt.Container.processEvent(Container.java:2010) at java.awt.Component.dispatchEventImpl(Component.java:4068) at java.awt.Container.dispatchEventImpl(Container.java:2068) at java.awt.Component.dispatchEvent(Component.java:3903) at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4256) at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3936) at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3866) at java.awt.Container.dispatchEventImpl(Container.java:2054) at java.awt.Window.dispatchEventImpl(Window.java:1801) at java.awt.Component.dispatchEvent(Component.java:3903) at java.awt.EventQueue.dispatchEvent(EventQueue.java:463) at com.intellij.ide.IdeEventQueue.c(IdeEventQueue.java:35) at com.intellij.ide.IdeEventQueue.b(IdeEventQueue.java:223) at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:217) at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:269) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:180) at java.awt.Dialog$1.run(Dialog.java:535) at java.awt.Dialog$2.run(Dialog.java:565) at java.security.AccessController.doPrivileged(Native Method) at java.awt.Dialog.show(Dialog.java:563) at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl$MyDialog.show(DialogWrapperPeerImpl.java:3) at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.show(DialogWrapperPeerImpl.java:17) at com.intellij.openapi.ui.DialogWrapper.show(DialogWrapper.java:849) at com.atlassian.theplugin.idea.crucible.CruciblePatchUploader.run(CruciblePatchUploader.java:42) at com.intellij.openapi.application.impl.LaterInvocator$1.run(LaterInvocator.java:2) at com.intellij.openapi.application.impl.LaterInvocator$FlushQueue.run(LaterInvocator.java:27) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209) at java.awt.EventQueue.dispatchEvent(EventQueue.java:461) at com.intellij.ide.IdeEventQueue.c(IdeEventQueue.java:35) at com.intellij.ide.IdeEventQueue.b(IdeEventQueue.java:99) at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:217) at com.intellij.ide.IdeEventQueue.pumpEventsForHierarchy(IdeEventQueue.java:163) at com.intellij.openapi.progress.util.ProgressWindow.startBlocking(ProgressWindow.java:125) at com.intellij.openapi.application.impl.ApplicationImpl.runProcessWithProgressSynchronously(ApplicationImpl.java:268) at com.intellij.openapi.progress.impl.ProgressManagerImpl.a(ProgressManagerImpl.java:70) at com.intellij.openapi.progress.impl.ProgressManagerImpl.runProcessWithProgressSynchronously(ProgressManagerImpl.java:103) at com.intellij.openapi.progress.impl.ProgressManagerImpl.runProcessWithProgressSynchronously(ProgressManagerImpl.java:49) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog$3.run(CommitChangeListDialog.java:12) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog$5.run(CommitChangeListDialog.java:5) at com.intellij.openapi.vcs.checkin.StandardBeforeCheckinHandler$2.run(StandardBeforeCheckinHandler.java:4) at com.intellij.openapi.vcs.checkin.StandardBeforeCheckinHandler$3.run(StandardBeforeCheckinHandler.java:1) at com.intellij.openapi.vcs.checkin.StandardBeforeCheckinHandler.runCheckinHandlers(StandardBeforeCheckinHandler.java:24) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog.a(CommitChangeListDialog.java:259) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog.a(CommitChangeListDialog.java:329) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog.access$1400(CommitChangeListDialog.java:26) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog$CommitExecutorAction.actionPerformed(CommitChangeListDialog.java:5) at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1882) at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2202) at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:420) at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:258) at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236) at java.awt.Component.processMouseEvent(Component.java:5602) at javax.swing.JComponent.processMouseEvent(JComponent.java:3135) at java.awt.Component.processEvent(Component.java:5367) at java.awt.Container.processEvent(Container.java:2010) at java.awt.Component.dispatchEventImpl(Component.java:4068) at java.awt.Container.dispatchEventImpl(Container.java:2068) at java.awt.Component.dispatchEvent(Component.java:3903) at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4256) at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3936) at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3866) at java.awt.Container.dispatchEventImpl(Container.java:2054) at java.awt.Window.dispatchEventImpl(Window.java:1801) at java.awt.Component.dispatchEvent(Component.java:3903) at java.awt.EventQueue.dispatchEvent(EventQueue.java:463) at com.intellij.ide.IdeEventQueue.c(IdeEventQueue.java:35) at com.intellij.ide.IdeEventQueue.b(IdeEventQueue.java:223) at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:217) at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:269) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:180) at java.awt.Dialog$1.run(Dialog.java:535) at java.awt.Dialog$2.run(Dialog.java:565) at java.security.AccessController.doPrivileged(Native Method) at java.awt.Dialog.show(Dialog.java:563) at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl$MyDialog.show(DialogWrapperPeerImpl.java:3) at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.show(DialogWrapperPeerImpl.java:17) at com.intellij.openapi.ui.DialogWrapper.show(DialogWrapper.java:849) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog.a(CommitChangeListDialog.java:167) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog.commitChanges(CommitChangeListDialog.java:31) at com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog.commitChanges(CommitChangeListDialog.java:81) at com.intellij.openapi.vcs.actions.AbstractCommonCheckinAction$1.run(AbstractCommonCheckinAction.java:7) at com.intellij.openapi.vcs.changes.Waiter$1.run(Waiter.java:9) at com.intellij.openapi.application.impl.LaterInvocator$FlushQueue.run(LaterInvocator.java:27) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209) at java.awt.EventQueue.dispatchEvent(EventQueue.java:461) at com.intellij.ide.IdeEventQueue.c(IdeEventQueue.java:35) at com.intellij.ide.IdeEventQueue.b(IdeEventQueue.java:99) at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:217) at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:269) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:184) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:176) at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
    via by Marek Went,
    • org.jdom.IllegalDataException: The data "........... (here I've cut our case description, it contains ]]>)" " is not legal for a JDOM CDATA section: CDATA cannot internally contain a CDATA ending delimiter (]]>). at org.jdom.CDATA.setText(CDATA.java:121) at org.jdom.CDATA.<init>(CDATA.java:95) at org.jdom.DefaultJDOMFactory.cdata(DefaultJDOMFactory.java:97) at org.jdom.input.SAXHandler.flushCharacters(SAXHandler.java:652) at org.jdom.input.SAXHandler.flushCharacters(SAXHandler.java:623) at org.jdom.input.SAXHandler.endElement(SAXHandler.java:678) at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source) at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanEndElement(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XMLParser.parse(Unknown Source) at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source) at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source) at org.jdom.input.SAXBuilder.build(SAXBuilder.java:453) at org.jdom.input.SAXBuilder.build(SAXBuilder.java:770) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzClient.getWithToken(FogBugzClient.java:223) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzClient.getCases(FogBugzClient.java:275) at com.atlassian.jira.plugins.importer.imports.fogbugz.hosted.FogBugzHostedDataBean.getIssuesIterator(FogBugzHostedDataBean.java:132) at com.atlassian.jira.plugins.importer.imports.importer.impl.DefaultJiraDataImporter.importIssues(DefaultJiraDataImporter.java:833) at com.atlassian.jira.plugins.importer.imports.importer.impl.DefaultJiraDataImporter.doImport(DefaultJiraDataImporter.java:428) at com.atlassian.jira.plugins.importer.imports.importer.impl.ImporterCallable.call(ImporterCallable.java:26) at com.atlassian.jira.plugins.importer.imports.importer.impl.ImporterCallable.call(ImporterCallable.java:15) at com.atlassian.jira.task.TaskManagerImpl$TaskCallableDecorator.call(TaskManagerImpl.java:374) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at com.atlassian.jira.task.ForkedThreadExecutor$ForkedRunnableDecorator.run(ForkedThreadExecutor.java:250) at java.lang.Thread.run(Thread.java:662)
    No Bugmate found.