This is an automated email from the ASF dual-hosted git repository. sjaranowski pushed a commit to branch MCHANGES-428 in repository https://gitbox.apache.org/repos/asf/maven-changes-plugin.git
commit b6624b0580baf62a433cf5adb96626c03b95a3ef Author: Slawomir Jaranowski <s.jaranow...@gmail.com> AuthorDate: Sat Nov 23 19:51:20 2024 +0100 [MCHANGES-428] Use HttpClient in RestJiraDownloader --- pom.xml | 22 +- src/it/report-jira/pom.xml | 78 +++ src/it/report-jira/setup.groovy | 123 +++++ .../report-jira/verify.groovy} | 30 +- src/it/settings-security.xml | 22 + src/it/settings.xml | 9 + .../plugins/announcement/AnnouncementMojo.java | 33 +- .../org/apache/maven/plugins/issues/Issue.java | 13 - .../maven/plugins/jira/AbstractJiraDownloader.java | 357 ------------- .../org/apache/maven/plugins/jira/JiraReport.java | 57 +- .../org/apache/maven/plugins/jira/JiraXML.java | 198 ------- .../maven/plugins/jira/RestJiraDownloader.java | 583 +++++++++++++++------ .../apache/maven/plugins/jira/JiraReportTest.java | 2 +- .../maven/plugins/jira/JiraUnicodeTestCase.java | 26 +- .../maven/plugins/jira/MockJiraDownloader.java | 57 -- .../maven/plugins/jira/unicode-jira-results.xml | 330 ------------ 16 files changed, 738 insertions(+), 1202 deletions(-) diff --git a/pom.xml b/pom.xml index 474e20c..9caedc7 100644 --- a/pom.xml +++ b/pom.xml @@ -300,12 +300,6 @@ under the License. <version>${doxiaVersion}</version> </dependency> - <!-- rest client for jira --> - <dependency> - <groupId>org.apache.cxf</groupId> - <artifactId>cxf-rt-frontend-jaxrs</artifactId> - <version>2.6.11</version> - </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> @@ -375,6 +369,12 @@ under the License. <version>${mavenResolverVersion}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>com.github.tomakehurst</groupId> + <artifactId>wiremock-standalone</artifactId> + <version>2.27.2</version> + <scope>test</scope> + </dependency> </dependencies> <build> @@ -459,7 +459,17 @@ under the License. <goal>clean</goal> <goal>site</goal> </goals> + <properties> + <settings.security>${project.basedir}/src/it/settings-security.xml</settings.security> + </properties> </configuration> + <dependencies> + <dependency> + <groupId>com.github.tomakehurst</groupId> + <artifactId>wiremock-standalone</artifactId> + <version>2.27.2</version> + </dependency> + </dependencies> </plugin> </plugins> </pluginManagement> diff --git a/src/it/report-jira/pom.xml b/src/it/report-jira/pom.xml new file mode 100644 index 0000000..49c3ed5 --- /dev/null +++ b/src/it/report-jira/pom.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-changes-plugin-test</artifactId> + <version>99.0</version> + <name>Maven</name> + <packaging>jar</packaging> + + <properties> + <changesPluginVersion>@project.version@</changesPluginVersion> + </properties> + + <issueManagement> + <system>jira</system> + <url>${mockServerUrl}/browse/TEST_PROJECT</url> + </issueManagement> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-changes-plugin</artifactId> + <version>${changesPluginVersion}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-site-plugin</artifactId> + <version>@sitePluginVersion@</version> + </plugin> + </plugins> + </pluginManagement> + </build> + + <reporting> + <excludeDefaults>true</excludeDefaults> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-changes-plugin</artifactId> + <version>@project.version@</version> + <reportSets> + <reportSet> + <reports> + <report>jira-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <jiraServerId>jira</jiraServerId> + </configuration> + </plugin> + </plugins> + </reporting> + +</project> diff --git a/src/it/report-jira/setup.groovy b/src/it/report-jira/setup.groovy new file mode 100644 index 0000000..a116a10 --- /dev/null +++ b/src/it/report-jira/setup.groovy @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.common.ConsoleNotifier +import com.github.tomakehurst.wiremock.core.WireMockConfiguration + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse +import static com.github.tomakehurst.wiremock.client.WireMock.configureFor +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson +import static com.github.tomakehurst.wiremock.client.WireMock.get +import static com.github.tomakehurst.wiremock.client.WireMock.post +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor + +WireMockServer wireMockServer = new WireMockServer(WireMockConfiguration.options() + .notifier(new ConsoleNotifier(false))) +wireMockServer.start() + +def userProperties = context.get('userProperties') +userProperties.put('mockServerUrl', wireMockServer.baseUrl()) + +configureFor(wireMockServer.port()) + +stubFor(get('/rest/api/2/serverInfo') + .withHeader('accept', equalTo('application/json')) + .willReturn(aResponse().withStatus(200))) + +stubFor(post('/rest/auth/1/session') + .withHeader('accept', equalTo('application/json')) + .withHeader('content-type', equalTo('application/json')) + .withRequestBody(equalToJson('{"username" : "jira-test", "password" : "jira-password"}')) + .willReturn(aResponse() + .withStatus(200) + .withHeader('Set-Cookie', 'JSESSIONID=D6961281D4A029FFA4E6BC8A526CF5CC; Path=/; HttpOnly'))) + +stubFor(get('/rest/api/2/status') + .withHeader('accept', equalTo('application/json')) + .withCookie('JSESSIONID', equalTo('D6961281D4A029FFA4E6BC8A526CF5CC')) + .willReturn(aResponse().withStatus(200) + .withBody('[{"name": "Closed","id": "6"}]'))) + +stubFor(get('/rest/api/2/resolution') + .withHeader('accept', equalTo('application/json')) + .withCookie('JSESSIONID', equalTo('D6961281D4A029FFA4E6BC8A526CF5CC')) + .willReturn(aResponse().withStatus(200) + .withBody('[{"name": "Fixed","id": "1"}]'))) + +stubFor(post('/rest/api/2/search') + .withHeader('accept', equalTo('application/json')) + .withHeader('content-type', equalTo('application/json')) + .withCookie('JSESSIONID', equalTo('D6961281D4A029FFA4E6BC8A526CF5CC')) + .withRequestBody(equalToJson(''' + {"jql":"project = TEST_PROJECT AND status in (6) AND resolution in (1) ORDER BY priority DESC, created DESC", + "maxResults":100, "fields":["*all"]} +''')) + .willReturn(aResponse().withStatus(200) + .withBody(''' +{ + "issues": [ + { + "id": "13036790", + "key": "TEST_PROJECT-1", + "fields": { + "assignee": { + "name": "assigned-user", + "displayName": "Assigned User" + }, + "created": "2024-11-21T12:10:35.000+0000", + "updated": "2024-11-22T20:05:52.000+0000", + "components": [ + { "name": "jira" } + ], + "fixVersions": [ + { "name": "2.1" }, + { "name": "3.1" } + ], + "issuetype": { + "name": "Bug" + }, + "priority": { + "name": "Bug" + }, + "reporter": { + "name": "reporter-user", + "displayName": "~Reporter User" + }, + "resolution": { + "name": "Fixed" + }, + "status": { + "name": "Closed" + }, + "summary": "Authentication does not work after Upgrade", + "versions": [ + { "name": "2.12.1" } + ] + } + }] +} +'''))) + +context.put("wireMockServer", wireMockServer) +true + diff --git a/src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java b/src/it/report-jira/verify.groovy similarity index 54% copy from src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java copy to src/it/report-jira/verify.groovy index a27babf..20f8f59 100644 --- a/src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java +++ b/src/it/report-jira/verify.groovy @@ -16,26 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.plugins.jira; -import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import com.github.tomakehurst.wiremock.WireMockServer -/** - * Unit tests for {@link JiraReport}. - * - * @author jrh3k5 - * @since 2.8 - */ -public class JiraReportTest extends AbstractMojoTestCase { - private final JiraReport mojo = new JiraReport(); +WireMockServer wireMockServer = context.get("wireMockServer") +wireMockServer.stop() + +content = new File( basedir, 'target/site/jira-report.html' ).text; - /** - * If the mojo has been marked to be skipped, then it should indicate that the report cannot be generated. - * - * @throws Exception If any errors occur during the test run. - */ - public void testCanGenerateReportSkipped() throws Exception { - setVariableValueToObject(mojo, "skip", Boolean.TRUE); - assertFalse(mojo.canGenerateReport()); - } -} +assert content.contains('/browse/TEST_PROJECT-1">TEST_PROJECT-1</a>'); +assert content.contains('<td>Authentication does not work after Upgrade</td>'); +assert content.contains('<td>Closed</td>'); +assert content.contains('<td>Fixed</td>'); +assert content.contains('<td>Assigned User</td>'); diff --git a/src/it/settings-security.xml b/src/it/settings-security.xml new file mode 100644 index 0000000..4a6047f --- /dev/null +++ b/src/it/settings-security.xml @@ -0,0 +1,22 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<settingsSecurity> + <master>{6TQXSXGMHWkHu6psfqOMMkXdy+tezkPMk5wcTB9B0VY=}</master> +</settingsSecurity> diff --git a/src/it/settings.xml b/src/it/settings.xml index c8f77f0..a57ccfa 100644 --- a/src/it/settings.xml +++ b/src/it/settings.xml @@ -52,4 +52,13 @@ under the License. </pluginRepositories> </profile> </profiles> + + <servers> + <server> + <id>jira</id> + <username>jira-test</username> + <!-- <password>jira-password</password> --> + <password>{xZLx2669VMsHnW8pO9Ehd1FLMrPsCOQ3g2MiRNNA5vI=}</password> + </server> + </servers> </settings> diff --git a/src/main/java/org/apache/maven/plugins/announcement/AnnouncementMojo.java b/src/main/java/org/apache/maven/plugins/announcement/AnnouncementMojo.java index a86ca3f..aa3f356 100644 --- a/src/main/java/org/apache/maven/plugins/announcement/AnnouncementMojo.java +++ b/src/main/java/org/apache/maven/plugins/announcement/AnnouncementMojo.java @@ -44,13 +44,11 @@ import org.apache.maven.plugins.github.GitHubIssueManagementSystem; import org.apache.maven.plugins.issues.Issue; import org.apache.maven.plugins.issues.IssueManagementSystem; import org.apache.maven.plugins.issues.IssueUtils; -import org.apache.maven.plugins.jira.AbstractJiraDownloader; import org.apache.maven.plugins.jira.JIRAIssueManagmentSystem; import org.apache.maven.plugins.jira.RestJiraDownloader; import org.apache.maven.plugins.trac.TracDownloader; import org.apache.maven.plugins.trac.TracIssueManagmentSystem; import org.apache.maven.project.MavenProject; -import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; import org.apache.maven.settings.crypto.SettingsDecrypter; import org.apache.velocity.Template; @@ -299,12 +297,6 @@ public class AnnouncementMojo extends AbstractAnnouncementMojo { @Parameter(property = "changes.jiraServerId") private String jiraServerId; - /** - * Path to the JIRA XML file, which will be parsed. - */ - @Parameter(defaultValue = "${project.build.directory}/jira-announcement.xml", required = true, readonly = true) - private File jiraXML; - /** * The maximum number of issues to fetch from JIRA. */ @@ -338,7 +330,9 @@ public class AnnouncementMojo extends AbstractAnnouncementMojo { * Defines the http user for basic authentication into the JIRA webserver. * * @since 2.4 + * @deprecated use {@link #jiraUser} or {@link #jiraServerId} */ + @Deprecated @Parameter(property = "changes.webUser") private String webUser; @@ -346,7 +340,9 @@ public class AnnouncementMojo extends AbstractAnnouncementMojo { * Defines the http password for basic authentication into the JIRA webserver. * * @since 2.4 + * @deprecated use {@link #jiraPassword} or {@link #jiraServerId} */ + @Deprecated @Parameter(property = "changes.webPassword") private String webPassword; @@ -684,14 +680,10 @@ public class AnnouncementMojo extends AbstractAnnouncementMojo { } protected List<Release> getJiraReleases() throws MojoExecutionException { - AbstractJiraDownloader jiraDownloader = new RestJiraDownloader(); - - File jiraXMLFile = jiraXML; + RestJiraDownloader jiraDownloader = new RestJiraDownloader(); jiraDownloader.setLog(getLog()); - jiraDownloader.setOutput(jiraXMLFile); - jiraDownloader.setStatusIds(statusIds); jiraDownloader.setResolutionIds(resolutionIds); @@ -699,24 +691,21 @@ public class AnnouncementMojo extends AbstractAnnouncementMojo { jiraDownloader.setMavenProject(project); jiraDownloader.setSettings(settings); + jiraDownloader.setSettingsDecrypter(settingsDecrypter); jiraDownloader.setNbEntries(maxEntries); jiraDownloader.setFilter(filter); - if (jiraServerId != null) { - final Server server = mavenSession.getSettings().getServer(jiraServerId); - jiraDownloader.setJiraUser(server.getUsername()); - jiraDownloader.setJiraPassword(server.getPassword()); - } else { + jiraDownloader.setJiraServerId(jiraServerId); + if (jiraUser != null) { jiraDownloader.setJiraUser(jiraUser); jiraDownloader.setJiraPassword(jiraPassword); + } else if (webUser != null) { + jiraDownloader.setJiraUser(webUser); + jiraDownloader.setJiraPassword(webPassword); } - jiraDownloader.setWebUser(webUser); - - jiraDownloader.setWebPassword(webPassword); - jiraDownloader.setConnectionTimeout(jiraConnectionTimeout); jiraDownloader.setReceiveTimout(jiraReceiveTimout); diff --git a/src/main/java/org/apache/maven/plugins/issues/Issue.java b/src/main/java/org/apache/maven/plugins/issues/Issue.java index 8947e0c..0ba003f 100644 --- a/src/main/java/org/apache/maven/plugins/issues/Issue.java +++ b/src/main/java/org/apache/maven/plugins/issues/Issue.java @@ -32,8 +32,6 @@ import java.util.List; public class Issue { private String assignee; - private List<String> comments; - private List<String> components; private Date created; @@ -74,17 +72,6 @@ public class Issue { this.assignee = assignee; } - public List<String> getComments() { - return comments; - } - - public void addComment(String comment) { - if (comments == null) { - comments = new ArrayList<>(); - } - comments.add(comment); - } - public List<String> getComponents() { return components; } diff --git a/src/main/java/org/apache/maven/plugins/jira/AbstractJiraDownloader.java b/src/main/java/org/apache/maven/plugins/jira/AbstractJiraDownloader.java deleted file mode 100644 index e417542..0000000 --- a/src/main/java/org/apache/maven/plugins/jira/AbstractJiraDownloader.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.maven.plugins.jira; - -import java.io.File; -import java.util.List; - -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.issues.Issue; -import org.apache.maven.plugins.issues.IssueUtils; -import org.apache.maven.project.MavenProject; -import org.apache.maven.settings.Proxy; -import org.apache.maven.settings.Settings; - -/** - * Abstract API, more or less, to retrieve issue information from JIRA. Has a subclass that uses - * the JIRA REST API. - * - * @author mfran...@xebia.com - * @author jr...@exist.com - * @version $Id$ - */ -public abstract class AbstractJiraDownloader { - protected static final String UTF_8 = "UTF-8"; - - /** Log for debug output. */ - protected Log log; - - /** Output file for xml document. */ - protected File output; - - /** The maximum number of entries to show. */ - protected int nbEntriesMax; - - /** The filter to apply to query to JIRA. */ - protected String filter; - - /** Ids of fix versions to show, as comma separated string. */ - protected String fixVersionIds; - - /** Ids of status to show, as comma separated string. */ - protected String statusIds; - - /** Ids of resolution to show, as comma separated string. */ - protected String resolutionIds; - - /** Ids of priority to show, as comma separated string. */ - protected String priorityIds; - - /** The component to show. */ - protected String component; - - /** Ids of types to show, as comma separated string. */ - protected String typeIds; - - /** Column names to sort by, as comma separated string. */ - protected String sortColumnNames; - - /** The username to log into JIRA. */ - protected String jiraUser; - - /** The password to log into JIRA. */ - protected String jiraPassword; - - /** The username to log into webserver. */ - protected String webUser; - - /** The password to log into webserver. */ - protected String webPassword; - - /** The maven project. */ - protected MavenProject project; - - /** The maven settings. */ - protected Settings settings; - - /** Filter the JIRA query based on the current version */ - protected boolean onlyCurrentVersion; - - /** The versionPrefix to apply to the POM version */ - protected String versionPrefix; - - /** The pattern used to parse dates from the JIRA xml file. */ - protected String jiraDatePattern; - - protected String proxyHost; - - protected int proxyPort; - - protected String proxyUser; - - protected String proxyPass; - - protected int connectionTimeout; - - protected int receiveTimout; - - /** - * Execute the query on the JIRA server. - * - * @throws Exception on error - */ - public abstract void doExecute() throws Exception; - - /** - * Check to see if we think that JIRA authentication is needed. - * - * @return <code>true</code> if jiraUser and jiraPassword are set, otherwise <code>false</code> - */ - protected boolean isJiraAuthenticationConfigured() { - return (jiraUser != null) && (jiraUser.length() > 0) && (jiraPassword != null); - } - - protected void getProxyInfo(String jiraUrl) { - // see whether there is any proxy defined in maven - Proxy proxy = null; - - if (project == null) { - getLog().error("No project set. No proxy info available."); - - return; - } - - if (settings != null) { - proxy = settings.getActiveProxy(); - } - - if (proxy != null) { - /// TODO check proxy.getNonProxyHosts() - proxyHost = settings.getActiveProxy().getHost(); - proxyPort = settings.getActiveProxy().getPort(); - proxyUser = settings.getActiveProxy().getUsername(); - proxyPass = settings.getActiveProxy().getPassword(); - } - } - - /** - * Override this method if you need to get issues for a specific Fix For. - * - * @return a Fix For id or <code>null</code> if you don't have that need - */ - protected String getFixFor() { - if (onlyCurrentVersion) { - // Let JIRA do the filtering of the current version instead of the JIRA mojo. - // This way JIRA returns less issues and we do not run into the "nbEntriesMax" limit that easily. - - String version = (versionPrefix == null ? "" : versionPrefix) + project.getVersion(); - - // Remove "-SNAPSHOT" from the end of the version, if it's there - if (version.endsWith(IssueUtils.SNAPSHOT_SUFFIX)) { - return version.substring(0, version.length() - IssueUtils.SNAPSHOT_SUFFIX.length()); - } else { - return version; - } - } else { - return null; - } - } - - public abstract List<Issue> getIssueList() throws MojoExecutionException; - - public void setJiraDatePattern(String jiraDatePattern) { - this.jiraDatePattern = jiraDatePattern; - } - - /** - * Set the output file for the log. - * - * @param thisOutput the output file - */ - public void setOutput(File thisOutput) { - this.output = thisOutput; - } - - public File getOutput() { - return this.output; - } - - /** - * Sets the project. - * - * @param thisProject The project to set - */ - public void setMavenProject(Object thisProject) { - this.project = (MavenProject) thisProject; - } - - /** - * Sets the maximum number of Issues to show. - * - * @param nbEntries The maximum number of Issues - */ - public void setNbEntries(final int nbEntries) { - nbEntriesMax = nbEntries; - } - - /** - * Sets the statusIds. - * - * @param thisStatusIds The id(s) of the status to show, as comma separated string - */ - public void setStatusIds(String thisStatusIds) { - statusIds = thisStatusIds; - } - - /** - * Sets the priorityIds. - * - * @param thisPriorityIds The id(s) of the priority to show, as comma separated string - */ - public void setPriorityIds(String thisPriorityIds) { - priorityIds = thisPriorityIds; - } - - /** - * Sets the resolutionIds. - * - * @param thisResolutionIds The id(s) of the resolution to show, as comma separated string - */ - public void setResolutionIds(String thisResolutionIds) { - resolutionIds = thisResolutionIds; - } - - /** - * Sets the sort column names. - * - * @param thisSortColumnNames The column names to sort by - */ - public void setSortColumnNames(String thisSortColumnNames) { - sortColumnNames = thisSortColumnNames; - } - - /** - * Sets the password for authentication against the webserver. - * - * @param thisWebPassword The password of the webserver - */ - public void setWebPassword(String thisWebPassword) { - this.webPassword = thisWebPassword; - } - - /** - * Sets the username for authentication against the webserver. - * - * @param thisWebUser The username of the webserver - */ - public void setWebUser(String thisWebUser) { - this.webUser = thisWebUser; - } - - /** - * Sets the password to log into a secured JIRA. - * - * @param thisJiraPassword The password for JIRA - */ - public void setJiraPassword(final String thisJiraPassword) { - this.jiraPassword = thisJiraPassword; - } - - /** - * Sets the username to log into a secured JIRA. - * - * @param thisJiraUser The username for JIRA - */ - public void setJiraUser(String thisJiraUser) { - this.jiraUser = thisJiraUser; - } - - /** - * Sets the filter to apply to query to JIRA. - * - * @param thisFilter The filter to query JIRA - */ - public void setFilter(String thisFilter) { - this.filter = thisFilter; - } - - /** - * Sets the component(s) to apply to query JIRA. - * - * @param theseComponents The id(s) of components to show, as comma separated string - */ - public void setComponent(String theseComponents) { - this.component = theseComponents; - } - - /** - * Sets the fix version id(s) to apply to query JIRA. - * - * @param theseFixVersionIds The id(s) of fix versions to show, as comma separated string - */ - public void setFixVersionIds(String theseFixVersionIds) { - this.fixVersionIds = theseFixVersionIds; - } - - /** - * Sets the typeIds. - * - * @param theseTypeIds The id(s) of the types to show, as comma separated string - */ - public void setTypeIds(String theseTypeIds) { - typeIds = theseTypeIds; - } - - public void setLog(Log log) { - this.log = log; - } - - protected Log getLog() { - return log; - } - - public void setSettings(Settings settings) { - this.settings = settings; - } - - public boolean isOnlyCurrentVersion() { - return onlyCurrentVersion; - } - - public void setOnlyCurrentVersion(boolean onlyCurrentVersion) { - this.onlyCurrentVersion = onlyCurrentVersion; - } - - public String getVersionPrefix() { - return versionPrefix; - } - - public void setVersionPrefix(String versionPrefix) { - this.versionPrefix = versionPrefix; - } - - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public void setReceiveTimout(int receiveTimout) { - this.receiveTimout = receiveTimout; - } -} diff --git a/src/main/java/org/apache/maven/plugins/jira/JiraReport.java b/src/main/java/org/apache/maven/plugins/jira/JiraReport.java index e22d2bb..38e17a6 100644 --- a/src/main/java/org/apache/maven/plugins/jira/JiraReport.java +++ b/src/main/java/org/apache/maven/plugins/jira/JiraReport.java @@ -18,7 +18,8 @@ */ package org.apache.maven.plugins.jira; -import java.io.File; +import javax.inject.Inject; + import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -35,8 +36,8 @@ import org.apache.maven.plugins.issues.IssueUtils; import org.apache.maven.plugins.issues.IssuesReportGenerator; import org.apache.maven.plugins.issues.IssuesReportHelper; import org.apache.maven.reporting.MavenReportException; -import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; +import org.apache.maven.settings.crypto.SettingsDecrypter; /** * Goal which downloads issues from the Issue Tracking System and generates a report. @@ -144,12 +145,6 @@ public class JiraReport extends AbstractChangesReport { @Parameter(property = "changes.jiraServerId") private String jiraServerId; - /** - * Path to the JIRA XML file, which will be parsed. - */ - @Parameter(defaultValue = "${project.build.directory}/jira-results.xml", required = true, readonly = true) - private File jiraXmlPath; - /** * Maximum number of entries to be fetched from JIRA. */ @@ -264,20 +259,36 @@ public class JiraReport extends AbstractChangesReport { /** * Defines the http password for basic authentication into the JIRA webserver. + * + * @deprecated use {@link #jiraPassword} or {@link #jiraServerId} */ + @Deprecated @Parameter private String webPassword; /** * Defines the http user for basic authentication into the JIRA webserver. + * + * @deprecated use {@link #jiraUser} or {@link #jiraServerId} */ + @Deprecated @Parameter private String webUser; /* * Used for tests. */ - private AbstractJiraDownloader mockDownloader; + private RestJiraDownloader mockDownloader; + + /** + * Component used to decrypt server information. + */ + private final SettingsDecrypter settingsDecrypter; + + @Inject + public JiraReport(SettingsDecrypter settingsDecrypter) { + this.settingsDecrypter = settingsDecrypter; + } /* --------------------------------------------------------------------- */ /* Public methods */ @@ -318,7 +329,7 @@ public class JiraReport extends AbstractChangesReport { try { // Download issues - AbstractJiraDownloader issueDownloader; + RestJiraDownloader issueDownloader; if (mockDownloader != null) { issueDownloader = mockDownloader; } else { @@ -378,13 +389,11 @@ public class JiraReport extends AbstractChangesReport { return ResourceBundle.getBundle("jira-report", locale, this.getClass().getClassLoader()); } - private void configureIssueDownloader(AbstractJiraDownloader issueDownloader) { + private void configureIssueDownloader(RestJiraDownloader issueDownloader) { issueDownloader.setLog(getLog()); issueDownloader.setMavenProject(project); - issueDownloader.setOutput(jiraXmlPath); - issueDownloader.setNbEntries(maxEntries); issueDownloader.setComponent(component); @@ -403,33 +412,27 @@ public class JiraReport extends AbstractChangesReport { issueDownloader.setJiraDatePattern(jiraDatePattern); - if (jiraServerId != null) { - final Server server = mavenSession.getSettings().getServer(jiraServerId); - issueDownloader.setJiraUser(server.getUsername()); - issueDownloader.setJiraPassword(server.getPassword()); - } else { + issueDownloader.setJiraServerId(jiraServerId); + + if (jiraUser != null) { issueDownloader.setJiraUser(jiraUser); issueDownloader.setJiraPassword(jiraPassword); + } else if (webUser != null) { + issueDownloader.setJiraUser(webUser); + issueDownloader.setJiraPassword(webPassword); } issueDownloader.setTypeIds(typeIds); - issueDownloader.setWebUser(webUser); - - issueDownloader.setWebPassword(webPassword); - issueDownloader.setSettings(settings); + issueDownloader.setSettingsDecrypter(settingsDecrypter); issueDownloader.setOnlyCurrentVersion(onlyCurrentVersion); issueDownloader.setVersionPrefix(versionPrefix); } - public void setMockDownloader(AbstractJiraDownloader mockDownloader) { + public void setMockDownloader(RestJiraDownloader mockDownloader) { this.mockDownloader = mockDownloader; } - - public AbstractJiraDownloader getMockDownloader() { - return mockDownloader; - } } diff --git a/src/main/java/org/apache/maven/plugins/jira/JiraXML.java b/src/main/java/org/apache/maven/plugins/jira/JiraXML.java deleted file mode 100644 index 2967c19..0000000 --- a/src/main/java/org/apache/maven/plugins/jira/JiraXML.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.maven.plugins.jira; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.issues.Issue; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.helpers.DefaultHandler; - -/** - * XML parser that extracts <code>Issue</code>s from JIRA. This works on an XML file downloaded from JIRA and creates a - * <code>List</code> of issues that is exposed to the user of the class. - * - * @version $Id$ - */ -public class JiraXML extends DefaultHandler { - private final List<Issue> issueList; - - private final StringBuilder currentElement = new StringBuilder(1024); - - private String currentParent = ""; - - private final String datePattern; - - private Issue issue; - - private String jiraVersion = null; - - private final Log log; - - private SimpleDateFormat sdf; - - /** - * @param log not null - * @param datePattern may be null - * @since 2.4 - */ - public JiraXML(Log log, String datePattern) { - this.log = log; - this.datePattern = datePattern; - - if (datePattern == null) { - sdf = null; - } else { - // @todo Do we need to be able to configure the locale of the JIRA server as well? - sdf = new SimpleDateFormat(datePattern, Locale.ENGLISH); - } - - this.issueList = new ArrayList<>(16); - } - - /** - * Parse the given XML file. The list of issues can then be retrieved with {@link #getIssueList()}. - * - * @param xmlPath the file to parse - * @throws MojoExecutionException in case of errors - * @since 2.4 - */ - public void parseXML(File xmlPath) throws MojoExecutionException { - try (InputStream xmlStream = new FileInputStream(xmlPath)) { - InputSource inputSource = new InputSource(xmlStream); - parse(inputSource); - } catch (FileNotFoundException e) { - throw new MojoExecutionException("Failed to open JIRA XML file " + xmlPath, e); - } catch (IOException e) { - throw new MojoExecutionException("Failed to read JIRA XML file " + xmlPath, e); - } - } - - void parse(InputSource xmlSource) throws MojoExecutionException { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser = factory.newSAXParser(); - - saxParser.parse(xmlSource, this); - } catch (Throwable t) { - throw new MojoExecutionException("Failed to parse JIRA XML.", t); - } - } - - public void startElement(String namespaceURI, String sName, String qName, Attributes attrs) { - switch (qName) { - case "item": - issue = new Issue(); - - currentParent = "item"; - break; - case "key": - String id = attrs.getValue("id"); - if (id != null) { - issue.setId(id.trim()); - } - break; - case "build-info": - currentParent = "build-info"; - break; - default: - // none - } - } - - public void endElement(String namespaceURI, String sName, String qName) { - if (qName.equals("item")) { - issueList.add(issue); - - currentParent = ""; - } else if (qName.equals("key")) { - issue.setKey(currentElement.toString().trim()); - } else if (qName.equals("summary")) { - issue.setSummary(currentElement.toString().trim()); - } else if (qName.equals("type")) { - issue.setType(currentElement.toString().trim()); - } else if (qName.equals("link") && currentParent.equals("item")) { - issue.setLink(currentElement.toString().trim()); - } else if (qName.equals("priority")) { - issue.setPriority(currentElement.toString().trim()); - } else if (qName.equals("status")) { - issue.setStatus(currentElement.toString().trim()); - } else if (qName.equals("resolution")) { - issue.setResolution(currentElement.toString().trim()); - } else if (qName.equals("assignee")) { - issue.setAssignee(currentElement.toString().trim()); - } else if (qName.equals("reporter")) { - issue.setReporter(currentElement.toString().trim()); - } else if (qName.equals("version") && currentParent.equals("item")) { - issue.setVersion(currentElement.toString().trim()); - } else if (qName.equals("version") && currentParent.equals("build-info")) { - jiraVersion = currentElement.toString().trim(); - } else if (qName.equals("fixVersion")) { - issue.addFixVersion(currentElement.toString().trim()); - } else if (qName.equals("component")) { - issue.addComponent(currentElement.toString().trim()); - } else if (qName.equals("comment")) { - issue.addComment(currentElement.toString().trim()); - } else if (qName.equals("title") && currentParent.equals("item")) { - issue.setTitle(currentElement.toString().trim()); - } else if (qName.equals("created") && currentParent.equals("item") && sdf != null) { - try { - issue.setCreated(sdf.parse(currentElement.toString().trim())); - } catch (ParseException e) { - log.warn("Element \"Created\". " + e.getMessage() + ". Using the pattern \"" + datePattern + "\""); - } - } else if (qName.equals("updated") && currentParent.equals("item") && sdf != null) { - try { - issue.setUpdated(sdf.parse(currentElement.toString().trim())); - } catch (ParseException e) { - log.warn("Element \"Updated\". " + e.getMessage() + ". Using the pattern \"" + datePattern + "\""); - } - } - - currentElement.setLength(0); - } - - public void characters(char[] buf, int offset, int len) { - currentElement.append(buf, offset, len); - } - - public List<Issue> getIssueList() { - return Collections.unmodifiableList(this.issueList); - } - - public String getJiraVersion() { - return jiraVersion; - } -} diff --git a/src/main/java/org/apache/maven/plugins/jira/RestJiraDownloader.java b/src/main/java/org/apache/maven/plugins/jira/RestJiraDownloader.java index f6a427e..abcda41 100644 --- a/src/main/java/org/apache/maven/plugins/jira/RestJiraDownloader.java +++ b/src/main/java/org/apache/maven/plugins/jira/RestJiraDownloader.java @@ -18,16 +18,15 @@ */ package org.apache.maven.plugins.jira; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; +import java.net.URI; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -37,25 +36,104 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MappingJsonFactory; -import org.apache.cxf.configuration.security.AuthorizationPolicy; -import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy; -import org.apache.cxf.interceptor.LoggingInInterceptor; -import org.apache.cxf.interceptor.LoggingOutInterceptor; -import org.apache.cxf.jaxrs.client.ClientConfiguration; -import org.apache.cxf.jaxrs.client.WebClient; -import org.apache.cxf.message.Message; -import org.apache.cxf.transport.http.HTTPConduit; -import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; -import org.apache.cxf.transports.http.configuration.ProxyServerType; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicHeader; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.issues.Issue; +import org.apache.maven.plugins.issues.IssueUtils; +import org.apache.maven.project.MavenProject; +import org.apache.maven.settings.Proxy; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.Settings; +import org.apache.maven.settings.building.SettingsProblem; +import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.apache.maven.settings.crypto.SettingsDecryptionResult; /** * Use the JIRA REST API to download issues. This class assumes that the URL points to a copy of JIRA that * implements the REST API. */ -public class RestJiraDownloader extends AbstractJiraDownloader { +public class RestJiraDownloader { + + /** Log for debug output. */ + private Log log; + + /** The maven project. */ + private MavenProject project; + + /** The maven settings. */ + private Settings settings; + + private SettingsDecrypter settingsDecrypter; + + /** Filter the JIRA query based on the current version */ + private boolean onlyCurrentVersion; + + /** The versionPrefix to apply to the POM version */ + protected String versionPrefix; + + /** The maximum number of entries to show. */ + protected int nbEntriesMax; + + /** The filter to apply to query to JIRA. */ + protected String filter; + + /** Ids of fix versions to show, as comma separated string. */ + protected String fixVersionIds; + + /** Ids of status to show, as comma separated string. */ + protected String statusIds; + + /** Ids of resolution to show, as comma separated string. */ + protected String resolutionIds; + + /** Ids of priority to show, as comma separated string. */ + protected String priorityIds; + + /** The component to show. */ + protected String component; + + /** Ids of types to show, as comma separated string. */ + protected String typeIds; + + /** Column names to sort by, as comma separated string. */ + protected String sortColumnNames; + + /** The username to log into JIRA. */ + protected String jiraUser; + + /** The password to log into JIRA. */ + protected String jiraPassword; + + private String jiraServerId; + + /** The pattern used to parse dates from the JIRA xml file. */ + protected String jiraDatePattern; + + protected int connectionTimeout; + + protected int receiveTimout; + private List<Issue> issueList; private JsonFactory jsonFactory; @@ -74,6 +152,177 @@ public class RestJiraDownloader extends AbstractJiraDownloader { private List<String> resolvedPriorityIds; + /** + * Override this method if you need to get issues for a specific Fix For. + * + * @return a Fix For id or <code>null</code> if you don't have that need + */ + protected String getFixFor() { + if (onlyCurrentVersion) { + // Let JIRA do the filtering of the current version instead of the JIRA mojo. + // This way JIRA returns less issues and we do not run into the "nbEntriesMax" limit that easily. + + String version = (versionPrefix == null ? "" : versionPrefix) + project.getVersion(); + + // Remove "-SNAPSHOT" from the end of the version, if it's there + if (version.endsWith(IssueUtils.SNAPSHOT_SUFFIX)) { + return version.substring(0, version.length() - IssueUtils.SNAPSHOT_SUFFIX.length()); + } else { + return version; + } + } else { + return null; + } + } + + /** + * Sets the project. + * + * @param thisProject The project to set + */ + public void setMavenProject(MavenProject thisProject) { + this.project = thisProject; + } + + public void setLog(Log log) { + this.log = log; + } + + protected Log getLog() { + return log; + } + + public void setSettings(Settings settings) { + this.settings = settings; + } + + public void setSettingsDecrypter(SettingsDecrypter settingsDecrypter) { + this.settingsDecrypter = settingsDecrypter; + } + + public void setOnlyCurrentVersion(boolean onlyCurrentVersion) { + this.onlyCurrentVersion = onlyCurrentVersion; + } + + public void setVersionPrefix(String versionPrefix) { + this.versionPrefix = versionPrefix; + } + + public void setJiraDatePattern(String jiraDatePattern) { + this.jiraDatePattern = jiraDatePattern; + } + + /** + * Sets the maximum number of Issues to show. + * + * @param nbEntries The maximum number of Issues + */ + public void setNbEntries(final int nbEntries) { + nbEntriesMax = nbEntries; + } + + /** + * Sets the statusIds. + * + * @param thisStatusIds The id(s) of the status to show, as comma separated string + */ + public void setStatusIds(String thisStatusIds) { + statusIds = thisStatusIds; + } + + /** + * Sets the priorityIds. + * + * @param thisPriorityIds The id(s) of the priority to show, as comma separated string + */ + public void setPriorityIds(String thisPriorityIds) { + priorityIds = thisPriorityIds; + } + + /** + * Sets the resolutionIds. + * + * @param thisResolutionIds The id(s) of the resolution to show, as comma separated string + */ + public void setResolutionIds(String thisResolutionIds) { + resolutionIds = thisResolutionIds; + } + + /** + * Sets the sort column names. + * + * @param thisSortColumnNames The column names to sort by + */ + public void setSortColumnNames(String thisSortColumnNames) { + sortColumnNames = thisSortColumnNames; + } + + /** + * Sets the password to log into a secured JIRA. + * + * @param thisJiraPassword The password for JIRA + */ + public void setJiraPassword(final String thisJiraPassword) { + this.jiraPassword = thisJiraPassword; + } + + /** + * Sets the username to log into a secured JIRA. + * + * @param thisJiraUser The username for JIRA + */ + public void setJiraUser(String thisJiraUser) { + this.jiraUser = thisJiraUser; + } + + public void setJiraServerId(String jiraServerId) { + this.jiraServerId = jiraServerId; + } + + /** + * Sets the filter to apply to query to JIRA. + * + * @param thisFilter The filter to query JIRA + */ + public void setFilter(String thisFilter) { + this.filter = thisFilter; + } + + /** + * Sets the component(s) to apply to query JIRA. + * + * @param theseComponents The id(s) of components to show, as comma separated string + */ + public void setComponent(String theseComponents) { + this.component = theseComponents; + } + + /** + * Sets the fix version id(s) to apply to query JIRA. + * + * @param theseFixVersionIds The id(s) of fix versions to show, as comma separated string + */ + public void setFixVersionIds(String theseFixVersionIds) { + this.fixVersionIds = theseFixVersionIds; + } + + /** + * Sets the typeIds. + * + * @param theseTypeIds The id(s) of the types to show, as comma separated string + */ + public void setTypeIds(String theseTypeIds) { + typeIds = theseTypeIds; + } + + public void setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public void setReceiveTimout(int receiveTimout) { + this.receiveTimout = receiveTimout; + } + public static class NoRest extends Exception { private static final long serialVersionUID = 6970088805270319624L; @@ -105,24 +354,16 @@ public class RestJiraDownloader extends AbstractJiraDownloader { String jiraUrl = urlMap.get("url"); String jiraProject = urlMap.get("project"); - WebClient client = setupWebClient(jiraUrl); - - // We use version 2 of the REST API, that first appeared in JIRA 5 - // Check if version 2 of the REST API is supported - // http://docs.atlassian.com/jira/REST/5.0/ - // Note that serverInfo can always be accessed without authentication - client.replacePath("/rest/api/2/serverInfo"); - client.accept(MediaType.APPLICATION_JSON); - Response siResponse = client.get(); - if (siResponse.getStatus() != Response.Status.OK.getStatusCode()) { - throw new NoRest("This JIRA server does not support version 2 of the REST API, " - + "which maven-changes-plugin requires."); + try (CloseableHttpClient client = setupHttpClient(jiraUrl)) { + checkRestApi(client, jiraUrl); + doSessionAuth(client, jiraUrl); + resolveIds(client, jiraUrl, jiraProject); + search(client, jiraProject, jiraUrl); } + } - doSessionAuth(client); - - resolveIds(client, jiraProject); - + private void search(CloseableHttpClient client, String jiraProject, String jiraUrl) + throws IOException, MojoExecutionException { String jqlQuery = new JqlQueryBuilder(log) .urlEncode(false) .project(jiraProject) @@ -137,6 +378,8 @@ public class RestJiraDownloader extends AbstractJiraDownloader { .filter(filter) .build(); + log.debug("JIRA jql=" + jqlQuery); + StringWriter searchParamStringWriter = new StringWriter(); try (JsonGenerator gen = jsonFactory.createGenerator(searchParamStringWriter)) { gen.writeStartObject(); @@ -148,32 +391,53 @@ public class RestJiraDownloader extends AbstractJiraDownloader { gen.writeEndArray(); gen.writeEndObject(); } - client.replacePath("/rest/api/2/search"); - client.type(MediaType.APPLICATION_JSON_TYPE); - client.accept(MediaType.APPLICATION_JSON_TYPE); - Response searchResponse = client.post(searchParamStringWriter.toString()); - if (searchResponse.getStatus() != Response.Status.OK.getStatusCode()) { - reportErrors(searchResponse); + + HttpPost httpPost = new HttpPost(jiraUrl + "/rest/api/2/search"); + httpPost.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setEntity(new StringEntity(searchParamStringWriter.toString())); + + try (CloseableHttpResponse response = client.execute(httpPost)) { + + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + reportErrors(response); + } + + JsonNode issueTree = getResponseTree(response); + + assertIsObject(issueTree); + JsonNode issuesNode = issueTree.get("issues"); + assertIsArray(issuesNode); + buildIssues(issuesNode, jiraUrl); } + } - JsonNode issueTree = getResponseTree(searchResponse); - assert issueTree.isObject(); - JsonNode issuesNode = issueTree.get("issues"); - assert issuesNode.isArray(); - buildIssues(issuesNode, jiraUrl, jiraProject); + private void checkRestApi(CloseableHttpClient client, String jiraUrl) throws IOException, NoRest { + // We use version 2 of the REST API, that first appeared in JIRA 5 + // Check if version 2 of the REST API is supported + // http://docs.atlassian.com/jira/REST/5.0/ + // Note that serverInfo can always be accessed without authentication + + HttpGet httpGet = new HttpGet(jiraUrl + "/rest/api/2/serverInfo"); + try (CloseableHttpResponse response = client.execute(httpGet)) { + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + throw new NoRest("This JIRA server does not support version 2 of the REST API, " + + "which maven-changes-plugin requires."); + } + } } - private JsonNode getResponseTree(Response response) throws IOException { - JsonParser jsonParser = jsonFactory.createParser((InputStream) response.getEntity()); - return (JsonNode) jsonParser.readValueAsTree(); + private JsonNode getResponseTree(HttpResponse response) throws IOException { + try (InputStream inputStream = response.getEntity().getContent(); + JsonParser jsonParser = jsonFactory.createParser(inputStream)) { + return jsonParser.readValueAsTree(); + } } - private void reportErrors(Response resp) throws IOException, MojoExecutionException { - if (MediaType.APPLICATION_JSON_TYPE - .getType() - .equals(getResponseMediaType(resp).getType())) { + private void reportErrors(HttpResponse resp) throws IOException, MojoExecutionException { + ContentType contentType = ContentType.get(resp.getEntity()); + if (contentType != null && contentType.getMimeType().equals(ContentType.APPLICATION_JSON.getMimeType())) { JsonNode errorTree = getResponseTree(resp); - assert errorTree.isObject(); + assertIsObject(errorTree); JsonNode messages = errorTree.get("errorMessages"); if (messages != null) { for (int mx = 0; mx < messages.size(); mx++) { @@ -186,59 +450,50 @@ public class RestJiraDownloader extends AbstractJiraDownloader { } } } - throw new MojoExecutionException(String.format("Failed to query issues; response %d", resp.getStatus())); + throw new MojoExecutionException(String.format( + "Failed to query issues; response %d", resp.getStatusLine().getStatusCode())); } - private void resolveIds(WebClient client, String jiraProject) + private void resolveIds(CloseableHttpClient client, String jiraUrl, String jiraProject) throws IOException, MojoExecutionException, MojoFailureException { resolveList( resolvedComponentIds, client, "components", component, - "/rest/api/2/project/{key}/components", - jiraProject); + jiraUrl + "/rest/api/2/project/" + jiraProject + "/components"); resolveList( resolvedFixVersionIds, client, "fixVersions", fixVersionIds, - "/rest/api/2/project/{key}/versions", - jiraProject); - resolveList(resolvedStatusIds, client, "status", statusIds, "/rest/api/2/status"); - resolveList(resolvedResolutionIds, client, "resolution", resolutionIds, "/rest/api/2/resolution"); - resolveList(resolvedTypeIds, client, "type", typeIds, "/rest/api/2/issuetype"); - resolveList(resolvedPriorityIds, client, "priority", priorityIds, "/rest/api/2/priority"); + jiraUrl + "/rest/api/2/project/" + jiraProject + "/versions"); + resolveList(resolvedStatusIds, client, "status", statusIds, jiraUrl + "/rest/api/2/status"); + resolveList(resolvedResolutionIds, client, "resolution", resolutionIds, jiraUrl + "/rest/api/2/resolution"); + resolveList(resolvedTypeIds, client, "type", typeIds, jiraUrl + "/rest/api/2/issuetype"); + resolveList(resolvedPriorityIds, client, "priority", priorityIds, jiraUrl + "/rest/api/2/priority"); } private void resolveList( - List<String> targetList, - WebClient client, - String what, - String input, - String listRestUrlPattern, - String... listUrlArgs) + List<String> targetList, CloseableHttpClient client, String what, String input, String listRestUrlPattern) throws IOException, MojoExecutionException, MojoFailureException { - if (input == null || input.length() == 0) { + if (input == null || input.isEmpty()) { return; } - if (listUrlArgs != null && listUrlArgs.length != 0) { - client.replacePath("/"); - client.path(listRestUrlPattern, listUrlArgs); - } else { - client.replacePath(listRestUrlPattern); - } - client.accept(MediaType.APPLICATION_JSON); - Response resp = client.get(); - if (resp.getStatus() != Response.Status.OK.getStatusCode()) { - getLog().error(String.format("Could not get %s list from %s", what, listRestUrlPattern)); - reportErrors(resp); - } - JsonNode items = getResponseTree(resp); - String[] pieces = input.split(","); - for (String item : pieces) { - targetList.add(resolveOneItem(items, what, item.trim())); + HttpGet httpGet = new HttpGet(listRestUrlPattern); + + try (CloseableHttpResponse response = client.execute(httpGet)) { + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + getLog().error(String.format("Could not get %s list from %s", what, listRestUrlPattern)); + reportErrors(response); + } + + JsonNode items = getResponseTree(response); + String[] pieces = input.split(","); + for (String item : pieces) { + targetList.add(resolveOneItem(items, what, item.trim())); + } } } @@ -254,16 +509,11 @@ public class RestJiraDownloader extends AbstractJiraDownloader { throw new MojoFailureException(String.format("Could not find %s %s.", what, nameOrId)); } - private MediaType getResponseMediaType(Response response) { - String header = (String) response.getMetadata().getFirst(HttpHeaders.CONTENT_TYPE); - return header == null ? null : MediaType.valueOf(header); - } - - private void buildIssues(JsonNode issuesNode, String jiraUrl, String jiraProject) { + private void buildIssues(JsonNode issuesNode, String jiraUrl) { issueList = new ArrayList<>(); for (int ix = 0; ix < issuesNode.size(); ix++) { JsonNode issueNode = issuesNode.get(ix); - assert issueNode.isObject(); + assertIsObject(issueNode); Issue issue = new Issue(); JsonNode val; @@ -287,9 +537,6 @@ public class RestJiraDownloader extends AbstractJiraDownloader { val = fieldsNode.get("created"); processCreated(issue, val); - val = fieldsNode.get("comment"); - processComments(issue, val); - val = fieldsNode.get("components"); processComponents(issue, val); @@ -419,7 +666,7 @@ public class RestJiraDownloader extends AbstractJiraDownloader { private void processFixVersions(Issue issue, JsonNode val) { if (val != null) { - assert val.isArray(); + assertIsArray(val); for (int vx = 0; vx < val.size(); vx++) { JsonNode fvNode = val.get(vx); issue.addFixVersion(fvNode.get("name").asText()); @@ -427,19 +674,9 @@ public class RestJiraDownloader extends AbstractJiraDownloader { } } - private void processComments(Issue issue, JsonNode val) { - if (val != null) { - JsonNode commentsArray = val.get("comments"); - for (int cx = 0; cx < commentsArray.size(); cx++) { - JsonNode cnode = commentsArray.get(cx); - issue.addComment(cnode.get("body").asText()); - } - } - } - private void processComponents(Issue issue, JsonNode val) { if (val != null) { - assert val.isArray(); + assertIsArray(val); for (int cx = 0; cx < val.size(); cx++) { JsonNode cnode = val.get(cx); issue.addComponent(cnode.get("name").asText()); @@ -453,11 +690,35 @@ public class RestJiraDownloader extends AbstractJiraDownloader { } } - private void doSessionAuth(WebClient client) throws IOException, MojoExecutionException, NoRest { - /* if JiraUser is specified instead of WebUser, we need to make a session. */ + private void assertIsObject(JsonNode node) { + if (!node.isObject()) { + throw new IllegalArgumentException("json node: " + node + " is not an object"); + } + } + + private void assertIsArray(JsonNode node) { + if (!node.isArray()) { + throw new IllegalArgumentException("json node: " + node + " is not an array"); + } + } + + private void doSessionAuth(CloseableHttpClient client, String jiraUrl) + throws IOException, MojoExecutionException, NoRest { + + Server server = settings.getServer(jiraServerId); + if (server != null) { + SettingsDecryptionResult result = settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(server)); + if (!result.getProblems().isEmpty()) { + for (SettingsProblem problem : result.getProblems()) { + log.error(problem.getMessage()); + } + } else { + jiraUser = result.getServer().getUsername(); + jiraPassword = result.getServer().getPassword(); + } + } + if (jiraUser != null) { - client.replacePath("/rest/auth/1/session"); - client.type(MediaType.APPLICATION_JSON_TYPE); StringWriter jsWriter = new StringWriter(); try (JsonGenerator gen = jsonFactory.createGenerator(jsWriter)) { gen.writeStartObject(); @@ -465,68 +726,72 @@ public class RestJiraDownloader extends AbstractJiraDownloader { gen.writeStringField("password", jiraPassword); gen.writeEndObject(); } - Response authRes = client.post(jsWriter.toString()); - if (authRes.getStatus() != Response.Status.OK.getStatusCode()) { - if (authRes.getStatus() != Response.Status.UNAUTHORIZED.getStatusCode() - && authRes.getStatus() != Response.Status.FORBIDDEN.getStatusCode()) { - // if not one of the documented failures, assume that there's no rest in there in the first place. - throw new NoRest(); + + HttpPost post = new HttpPost(jiraUrl + "/rest/auth/1/session"); + post.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); + post.setEntity(new StringEntity(jsWriter.toString())); + + try (CloseableHttpResponse response = client.execute(post)) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + if (statusCode != HttpStatus.SC_UNAUTHORIZED && statusCode != HttpStatus.SC_FORBIDDEN) { + // if not one of the documented failures, assume that there's no rest in there in the first + // place. + throw new NoRest(); + } + throw new MojoExecutionException(String.format("Authentication failure status %d.", statusCode)); } - throw new MojoExecutionException( - String.format("Authentication failure status %d.", authRes.getStatus())); } } } - private WebClient setupWebClient(String jiraUrl) { - WebClient client = WebClient.create(jiraUrl); - - ClientConfiguration clientConfiguration = WebClient.getConfig(client); - HTTPConduit http = clientConfiguration.getHttpConduit(); - // MCHANGES-324 - Maintain the client session - clientConfiguration.getRequestContext().put(Message.MAINTAIN_SESSION, Boolean.TRUE); - - if (getLog().isDebugEnabled()) { - clientConfiguration.getInInterceptors().add(new LoggingInInterceptor()); - clientConfiguration.getOutInterceptors().add(new LoggingOutInterceptor()); + private CloseableHttpClient setupHttpClient(String jiraUrl) { + + HttpClientBuilder httpClientBuilder = HttpClients.custom() + .setDefaultCookieStore(new BasicCookieStore()) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(receiveTimout) + .setConnectTimeout(connectionTimeout) + .build()) + .setDefaultHeaders(Collections.singletonList(new BasicHeader("Accept", "application/json"))); + + Proxy proxy = getProxy(jiraUrl); + if (proxy != null) { + if (proxy.getUsername() != null && proxy.getPassword() != null) { + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope(proxy.getHost(), proxy.getPort()), + new UsernamePasswordCredentials(proxy.getUsername(), proxy.getPassword())); + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + } + httpClientBuilder.setProxy(new HttpHost(proxy.getHost(), proxy.getPort())); } + return httpClientBuilder.build(); + } - HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); - - // MCHANGES-341 Externalize JIRA server timeout values to the configuration section - getLog().debug("RestJiraDownloader: connectionTimeout: " + connectionTimeout); - httpClientPolicy.setConnectionTimeout(connectionTimeout); - httpClientPolicy.setAllowChunking(false); - getLog().debug("RestJiraDownloader: receiveTimout: " + receiveTimout); - httpClientPolicy.setReceiveTimeout(receiveTimout); - - // MCHANGES-334 RestJiraDownloader doesn't honor proxy settings - getProxyInfo(jiraUrl); - - if (proxyHost != null) { - getLog().debug("Using proxy: " + proxyHost + " at port " + proxyPort); - httpClientPolicy.setProxyServer(proxyHost); - httpClientPolicy.setProxyServerPort(proxyPort); - httpClientPolicy.setProxyServerType(ProxyServerType.HTTP); - if (proxyUser != null) { - ProxyAuthorizationPolicy proxyAuthorizationPolicy = new ProxyAuthorizationPolicy(); - proxyAuthorizationPolicy.setAuthorizationType("Basic"); - proxyAuthorizationPolicy.setUserName(proxyUser); - proxyAuthorizationPolicy.setPassword(proxyPass); - http.setProxyAuthorization(proxyAuthorizationPolicy); + private Proxy getProxy(String jiraUrl) { + Proxy proxy = settings.getActiveProxy(); + if (proxy != null) { + SettingsDecryptionResult result = settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(proxy)); + if (!result.getProblems().isEmpty()) { + for (SettingsProblem problem : result.getProblems()) { + log.error(problem.getMessage()); + } + } else { + proxy = result.getProxy(); } } - if (webUser != null) { - AuthorizationPolicy authPolicy = new AuthorizationPolicy(); - authPolicy.setAuthorizationType("Basic"); - authPolicy.setUserName(webUser); - authPolicy.setPassword(webPassword); - http.setAuthorization(authPolicy); + if (proxy != null && proxy.getNonProxyHosts() != null) { + URI jiraUri = URI.create(jiraUrl); + boolean nonProxy = Arrays.stream(proxy.getNonProxyHosts().split("\\|")) + .anyMatch(host -> !host.equalsIgnoreCase(jiraUri.getHost())); + if (nonProxy) { + return null; + } } - http.setClient(httpClientPolicy); - return client; + return proxy; } public List<Issue> getIssueList() { diff --git a/src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java b/src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java index a27babf..fefab6a 100644 --- a/src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java +++ b/src/test/java/org/apache/maven/plugins/jira/JiraReportTest.java @@ -27,7 +27,7 @@ import org.apache.maven.plugin.testing.AbstractMojoTestCase; * @since 2.8 */ public class JiraReportTest extends AbstractMojoTestCase { - private final JiraReport mojo = new JiraReport(); + private final JiraReport mojo = new JiraReport(null); /** * If the mojo has been marked to be skipped, then it should indicate that the report cannot be generated. diff --git a/src/test/java/org/apache/maven/plugins/jira/JiraUnicodeTestCase.java b/src/test/java/org/apache/maven/plugins/jira/JiraUnicodeTestCase.java index b429704..c5928b3 100644 --- a/src/test/java/org/apache/maven/plugins/jira/JiraUnicodeTestCase.java +++ b/src/test/java/org/apache/maven/plugins/jira/JiraUnicodeTestCase.java @@ -19,23 +19,23 @@ package org.apache.maven.plugins.jira; import java.io.File; -import java.io.InputStream; import java.util.Collections; import java.util.List; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import org.apache.maven.plugins.issues.Issue; import org.apache.maven.project.MavenProject; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.RemoteRepository; -import static java.nio.charset.StandardCharsets.UTF_8; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * @version $Id$ @@ -53,7 +53,7 @@ public class JiraUnicodeTestCase extends AbstractMojoTestCase { assertNotNull(pom); assertTrue(pom.exists()); - JiraReport mojo = (JiraReport) lookupMojo("jira-report", pom); + JiraReport mojo = lookupMojo("jira-report", pom); MavenProject project = new JiraUnicodeTestProjectStub(); MavenSession session = newMavenSession(project); @@ -80,15 +80,17 @@ public class JiraUnicodeTestCase extends AbstractMojoTestCase { setVariableValueToObject(mojo, "mavenSession", session); setVariableValueToObject(mojo, "mojoExecution", new MojoExecution(new Plugin(), "jira-report", "default")); - String jiraXml; - try (InputStream testJiraXmlStream = - JiraUnicodeTestCase.class.getResourceAsStream("unicode-jira-results.xml")) { - jiraXml = IOUtils.toString(testJiraXmlStream, UTF_8); - } + RestJiraDownloader mock = mock(RestJiraDownloader.class); + Issue issue = new Issue(); + issue.setKey("PCSUNIT-2"); + issue.setLink("http://pcsjira.slg.gr/browse/PCSUNIT-2"); + issue.setSummary("海龟一路下跌。 Απεικόνιση σε EXCEL των data των φορμών. Περίπτωση με πολλά blocks"); + issue.setStatus("Closed"); + issue.setResolution("Fixed"); + issue.setAssignee("Nikolaos Stais"); + when(mock.getIssueList()).thenReturn(Collections.singletonList(issue)); - MockJiraDownloader mockDownloader = new MockJiraDownloader(); - mockDownloader.setJiraXml(jiraXml); - mojo.setMockDownloader(mockDownloader); + mojo.setMockDownloader(mock); File outputDir = new File("target/jira-test-output"); outputDir.mkdirs(); mojo.setReportOutputDirectory(outputDir); diff --git a/src/test/java/org/apache/maven/plugins/jira/MockJiraDownloader.java b/src/test/java/org/apache/maven/plugins/jira/MockJiraDownloader.java deleted file mode 100644 index 1063e93..0000000 --- a/src/test/java/org/apache/maven/plugins/jira/MockJiraDownloader.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.maven.plugins.jira; - -import java.io.StringReader; -import java.util.List; - -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.issues.Issue; -import org.xml.sax.InputSource; - -/** - * Allow test cases in the jira mojo without actually talking to jira. - * - * @version $Id$ - */ -public class MockJiraDownloader extends AbstractJiraDownloader { - @Override - public void doExecute() { - // do nothing - } - - private String jiraXml; - - @Override - public List<Issue> getIssueList() throws MojoExecutionException { - JiraXML jira = new JiraXML(log, jiraDatePattern); - InputSource inputSource = new InputSource(new StringReader(jiraXml)); - jira.parse(inputSource); - log.info("The JIRA version is '" + jira.getJiraVersion() + "'"); - return jira.getIssueList(); - } - - public void setJiraXml(String jiraXml) { - this.jiraXml = jiraXml; - } - - public String getJiraXml() { - return jiraXml; - } -} diff --git a/src/test/resources/org/apache/maven/plugins/jira/unicode-jira-results.xml b/src/test/resources/org/apache/maven/plugins/jira/unicode-jira-results.xml deleted file mode 100644 index 2b268cc..0000000 --- a/src/test/resources/org/apache/maven/plugins/jira/unicode-jira-results.xml +++ /dev/null @@ -1,330 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> - -<!-- - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. ---> - -<!-- RSS generated by JIRA (Enterprise Edition, Version: 3.13.2-#335) at Wed Aug 05 14:43:20 EEST 2009 --> -<!-- If you wish to do custom client-side styling of RSS, uncomment this: -<?xml-stylesheet href="http://pcsjira.slg.gr/styles/jiraxml2html.xsl" type="text/xsl"?> ---> -<rss version="0.92" > -<channel> - <title>Professional Computer Services S.A. JIRA</title> - <link>http://pcsjira.slg.gr/secure/IssueNavigator.jspa?reset=true&pid=10101&status=6&resolution=1&sorter/field=created&sorter/order=DESC&sorter/field=priority&sorter/order=DESC</link> - <description>An XML representation of a search request</description> - <language>en-us</language> <issue start="0" end="2" total="2" /> <build-info> - <version>3.13.2</version> - <build-number>335</build-number> - <build-date>26-11-2008</build-date> - <edition>Enterprise</edition> - </build-info> - -<item> -<title>[PCSUNIT-2] 海龟一路下跌。 Απεικόνιση σε EXCEL των data των φορμών. Περίπτωση με πολλά blocks</title> -<link>http://pcsjira.slg.gr/browse/PCSUNIT-2</link> - - <description></description> - <environment></environment> - <key id="11137">PCSUNIT-2</key> - <summary>海龟一路下跌。 Απεικόνιση σε EXCEL των data των φορμών. Περίπτωση με πολλά blocks</summary> - - <type id="4" iconUrl="http://pcsjira.slg.gr/images/icons/improvement.gif">Improvement</type> - - - <priority id="6" iconUrl="http://pcsjira.slg.gr/images/icons/priority_minor.gif">Normal</priority> - <status id="6" iconUrl="http://pcsjira.slg.gr/images/icons/status_closed.gif">Closed</status> - <resolution id="1">Fixed</resolution> - - <security id="10000">Internal Issue</security> - - <assignee username="nikoss">Nikolaos Stais</assignee> - - <reporter username="nikoss">Nikolaos Stais</reporter> - - <created>Wed, 18 Mar 2009 11:04:28 +0200 (EET)</created> - <updated>Thu, 23 Apr 2009 13:22:19 +0300 (EEST)</updated> - - - - - <due></due> - - <votes>0</votes> - - - - <comments> - <comment id="11583" author="nikoss" created="Thu, 19 Mar 2009 16:25:17 +0200 (EET)" rolelevel="PCS Internal" >Εχει πραγματοποιηθεί μια πρώτη προσέγγιση κ υλοποίηση, χρειάζεται Τ	 [...] - </comments> - - <attachments> - </attachments> - - <subtasks> - </subtasks> - - <customfields> - <customfield id="customfield_10011" key="com.atlassian.jira.plugin.system.customfieldtypes:float"> - <customfieldname>Α/Η</customfieldname> - <customfieldvalues> - <customfieldvalue>2.0</customfieldvalue> - </customfieldvalues> - </customfield> - </customfields> - -</item> - -<item> -<title>[PCSUNIT-1] ΔΗΜΙΟΥΡΓΙΑ ΔΙΑΔΙΚΑΣΙΑΣ ΓΙΑ UNDO CHANGES</title> -<link>http://pcsjira.slg.gr/browse/PCSUNIT-1</link> - - <description></description> - <environment></environment> - <key id="10051">PCSUNIT-1</key> - <summary>ΔΗΜΙΟΥΡΓΙΑ ΔΙΑΔΙΚΑΣΙΑΣ ΓΙΑ UNDO CHANGES</summary> - - <type id="2" iconUrl="http://pcsjira.slg.gr/images/icons/newfeature.gif">New Feature</type> - - - <priority id="4" iconUrl="http://pcsjira.slg.gr/images/icons/priority_minor.gif">Minor</priority> - <status id="6" iconUrl="http://pcsjira.slg.gr/images/icons/status_closed.gif">Closed</status> - <resolution id="1">Fixed</resolution> - - <security id="10000">Internal Issue</security> - - <assignee username="nikoss">Nikolaos Stais</assignee> - - <reporter username="nikoss">Nikolaos Stais</reporter> - - <created>Wed, 4 Feb 2009 13:47:25 +0200 (EET)</created> - <updated>Wed, 13 May 2009 13:32:49 +0300 (EEST)</updated> - - - - - <due></due> - - <votes>0</votes> - - - - <comments> - <comment id="10039" author="nikoss" created="Thu, 5 Feb 2009 10:07:35 +0200 (EET)" >Έγινε μια πρώτη προσέγγιση και ενημέρωση συναδέλφων. Σε αναμονή δοκιμής α` [...] - <comment id="13509" author="nikoss" created="Wed, 13 May 2009 13:32:34 +0300 (EEST)" >&nbsp; -<br/> - -<br/> -Έχοντας λάβει κάποια requests από πελάτες οι οποίοι επιθυμούν να κάνουν undo σε batch ροές, -<br/> - -<br/> -θεωρώ ότι υπάρχει ένας εύκολος τρόπος να το πετύχουμε, προκειμένου να αποφύγουμε να κρατάμε ισ	 [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Το συγκεκριμένο θέμα δεν έχει εφαρμοστεί (από όσο ξέρω) με τρόπο που να εισάγουμε δεδομένα και να [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Τολμώ να κάνω μια πρόταση προς διερεύνηση ... J -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Θεωρητικά θα μπορούμε π.χ. να κάνουμε ενημέρωση με τιμή να βλέπουμε τι μερίδια έχουν κοπεί (καλών [...] -<br/> - -<br/> -οθόνη θα είναι σαν να μην έχουμε κάνει τίποτα. -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Η όλη δουλεία γίνεται με αλλαγές στα εξής σημεία. -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Στον ON-COMMIT trigger (form-level). -<br/> - -<br/> -Στον ΚΕΥ-COMMIT trigger (form-level) -<br/> - -<br/> -Στον KEY-EXIT trigger (form-level) -<br/> - -<br/> -Στον KEY-ENTQRY trigger (form-level) -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Οπωσδήποτε αν υπάρχουν αντίστοιχοι triggers σε block-level, τότε πρέπει να επεξεργαστούν κατάλληλα. -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Στην ουσία, δεν κάνουμε ποτέ commit (αλλά POST;?στέλνει τα records στη βάση), παρά μόνο πατώντας το κουμπί REAL COMMIT. -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Μπορείτε να δείτε τα form1.fmb , form2.fmb που υπάρχουν στο Y:\MFHELLAS_10G\Exedir -<br/> - -<br/> -ή και να τεστάρετε το εξής σενάριο(<a href="http://dioskouros:7778/forms90/f90servlet?config=test_undo">http://dioskouros:7778/forms90/f90servlet?config=test_undo</a>) -<br/> - -<br/> -στην πράξη: -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Η form1 βλέπει ένα απλό πίνακα με 2 στήλες. Κάθε φορά που εισάγω μια εγγραφή και πατάω F10 βλέπω μήνυμα -<br/> - -<br/> -&quot;1 record applied&quot; (η διαφορά φαίνεται κ εδώ, δηλ. δεν λέει: &quot;1 record applied and saved&quot;, λείπει το &quot;saved&quot;=δεν έχει κάνει commit στη βάση αλ& [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Αν καλέσουμε τη δεύτερη οθόνη form2, μπορούμε να κάνουμε query τις αλλαγές(insert,delete,update + F10) που πραγματοποιήσαμε στ [...] -<br/> - -<br/> -Αν στην ίδια οθόνη (form1) κάνουμε F7 χωρίς να κάνουμε F10 (μετά την αλλαγή) η οθόνη χάνει τις αλλαγές (προγρα&# [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Βγαίνοντας από τις δύο οθόνες χωρίς να πατήσουμε το button REAL COMMIT, όλες οι αλλαγές που κάναμε στην form1 και τις [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Τα insert, update, delete μπορούν να γίνουν και προγραμματιστικά. Στην οθόνη form1 στον ON-INSERT trigger του block, υπάρχει για λόγους τε` [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Παρακαλώ για τα σχόλια σας και ιδιαίτερα για τους ενδεχόμενους κινδύνους, αν σας το επιτρέπει ο & [...] -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -&nbsp; -<br/> - -<br/> -Ευχαριστώ πολύ, -<br/> - -<br/> -</comment> - </comments> - - <attachments> - </attachments> - - <subtasks> - </subtasks> - - <customfields> - <customfield id="customfield_10001" key="com.atlassian.jira.plugin.system.customfieldtypes:textfield"> - <customfieldname>Screen Code</customfieldname> - <customfieldvalues> - <customfieldvalue>ΘΕΩΡΙΤΙΚ Α ΕΦΑΡΜΟΓΗ ΣΕ ΟΛΕΣ ΤΙΣ ΟΘΟΝΕΣ</customfieldvalue> - - </customfieldvalues> - </customfield> - </customfields> - -</item> -</channel> -</rss>