1 package org.apache.maven.plugin.jira;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.commons.httpclient.Credentials;
23 import org.apache.commons.httpclient.Header;
24 import org.apache.commons.httpclient.HostConfiguration;
25 import org.apache.commons.httpclient.HttpClient;
26 import org.apache.commons.httpclient.HttpException;
27 import org.apache.commons.httpclient.HttpState;
28 import org.apache.commons.httpclient.HttpStatus;
29 import org.apache.commons.httpclient.StatusLine;
30 import org.apache.commons.httpclient.UsernamePasswordCredentials;
31 import org.apache.commons.httpclient.params.HttpClientParams;
32 import org.apache.commons.httpclient.auth.AuthScope;
33 import org.apache.commons.httpclient.methods.GetMethod;
34 import org.apache.maven.plugin.MojoExecutionException;
35 import org.apache.maven.plugin.issues.Issue;
36 import org.apache.maven.plugin.logging.Log;
37 import org.apache.maven.project.MavenProject;
38 import org.apache.maven.settings.Proxy;
39 import org.apache.maven.settings.Settings;
40 import org.apache.maven.wagon.proxy.ProxyInfo;
41 import org.codehaus.plexus.util.IOUtil;
42 import org.codehaus.plexus.util.StringUtils;
43
44 import java.io.File;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.net.MalformedURLException;
50 import java.net.URL;
51 import java.net.URLEncoder;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.Map;
57
58
59
60
61
62
63
64
65
66
67 public abstract class AbstractJiraDownloader
68 {
69 private static final String UTF_8 = "UTF-8";
70
71
72 protected Log log;
73
74 private File output;
75
76 private int nbEntriesMax;
77
78 private String filter;
79
80 private String fixVersionIds;
81
82 private String statusIds;
83
84 private String resolutionIds;
85
86 private String priorityIds;
87
88 private String component;
89
90 private String typeIds;
91
92 private String sortColumnNames;
93
94 private String jiraUser;
95
96 private String jiraPassword;
97
98 private String webUser;
99
100 private String webPassword;
101
102 private MavenProject project;
103
104 private Settings settings;
105
106 protected final Map<String,String> statusMap = new HashMap<String,String>( 8 );
107
108 protected final Map<String,String> resolutionMap = new HashMap<String,String>( 8 );
109
110 protected final Map<String,String> priorityMap = new HashMap<String,String>( 8 );
111
112 protected final Map<String,String> typeMap = new HashMap<String,String>( 8 );
113
114 protected String jiraDatePattern;
115
116
117
118
119
120
121 private String createFilter()
122 {
123
124 if ( ( this.filter != null ) && ( this.filter.length() > 0 ) )
125 {
126 return this.filter;
127 }
128
129 StringBuffer localFilter = new StringBuffer( 16 );
130
131
132 if ( fixVersionIds != null )
133 {
134 String[] fixVersions = fixVersionIds.split( "," );
135
136 for ( int i = 0; i < fixVersions.length; i++ )
137 {
138 if ( fixVersions[i].length() > 0 )
139 {
140 localFilter.append( "&fixfor=" ).append( fixVersions[i].trim() );
141 }
142 }
143 }
144
145
146 if ( statusIds != null )
147 {
148 String[] stats = statusIds.split( "," );
149 for ( String stat : stats )
150 {
151 stat = stat.trim();
152 String statusParam = statusMap.get( stat );
153
154 if ( statusParam != null )
155 {
156 localFilter.append( "&statusIds=" ).append( statusParam );
157 }
158 else
159 {
160
161 try
162 {
163 Integer.parseInt( stat );
164 localFilter.append( "&statusIds=" ).append( stat );
165 }
166 catch ( NumberFormatException nfe )
167 {
168 getLog().error( "maven-changes-plugin: invalid statusId " + stat );
169 }
170 }
171 }
172 }
173
174
175 if ( priorityIds != null )
176 {
177 String[] prios = priorityIds.split( "," );
178
179 for ( String prio : prios )
180 {
181 prio = prio.trim();
182 String priorityParam = priorityMap.get( prio );
183
184 if ( priorityParam != null )
185 {
186 localFilter.append( "&priorityIds=" ).append( priorityParam );
187 }
188 }
189 }
190
191
192 if ( resolutionIds != null )
193 {
194 String[] resos = resolutionIds.split( "," );
195
196 for ( String reso : resos )
197 {
198 reso = reso.trim();
199 String resoParam = resolutionMap.get( reso );
200
201 if ( resoParam != null )
202 {
203 localFilter.append( "&resolutionIds=" ).append( resoParam );
204 }
205 }
206 }
207
208
209 if ( component != null )
210 {
211 String[] components = component.split( "," );
212
213 for ( String component : components )
214 {
215 component = component.trim();
216 if ( component.length() > 0 )
217 {
218 localFilter.append( "&component=" ).append( component );
219 }
220 }
221 }
222
223
224 if ( typeIds != null )
225 {
226 String[] types = typeIds.split( "," );
227
228 for ( String type : types )
229 {
230 String typeParam = typeMap.get( type.trim() );
231
232 if ( typeParam != null )
233 {
234 localFilter.append( "&type=" ).append( typeParam );
235 }
236 }
237 }
238
239
240 int validSortColumnNames = 0;
241 if ( sortColumnNames != null )
242 {
243 String[] sortColumnNamesArray = sortColumnNames.split( "," );
244
245 for ( int i = sortColumnNamesArray.length - 1; i >= 0; i-- )
246 {
247 String lowerColumnName = sortColumnNamesArray[i].trim().toLowerCase( Locale.ENGLISH );
248 boolean descending = false;
249 String fieldName = null;
250 if ( lowerColumnName.endsWith( "desc" ) )
251 {
252 descending = true;
253 lowerColumnName = lowerColumnName.substring( 0, lowerColumnName.length() - 4 ).trim();
254 }
255 else if ( lowerColumnName.endsWith( "asc" ) )
256 {
257 descending = false;
258 lowerColumnName = lowerColumnName.substring( 0, lowerColumnName.length() - 3 ).trim();
259 }
260
261 if ( "key".equals( lowerColumnName ) )
262 {
263 fieldName = "issuekey";
264 }
265 else if ( "summary".equals( lowerColumnName ) )
266 {
267 fieldName = lowerColumnName;
268 }
269 else if ( "status".equals( lowerColumnName ) )
270 {
271 fieldName = lowerColumnName;
272 }
273 else if ( "resolution".equals( lowerColumnName ) )
274 {
275 fieldName = lowerColumnName;
276 }
277 else if ( "assignee".equals( lowerColumnName ) )
278 {
279 fieldName = lowerColumnName;
280 }
281 else if ( "reporter".equals( lowerColumnName ) )
282 {
283 fieldName = lowerColumnName;
284 }
285 else if ( "type".equals( lowerColumnName ) )
286 {
287 fieldName = "issuetype";
288 }
289 else if ( "priority".equals( lowerColumnName ) )
290 {
291 fieldName = lowerColumnName;
292 }
293 else if ( "version".equals( lowerColumnName ) )
294 {
295 fieldName = "versions";
296 }
297 else if ( "fix version".equals( lowerColumnName ) )
298 {
299 fieldName = "fixVersions";
300 }
301 else if ( "component".equals( lowerColumnName ) )
302 {
303 fieldName = "components";
304 }
305 else if ( "created".equals( lowerColumnName ) )
306 {
307 fieldName = lowerColumnName;
308 }
309 else if ( "updated".equals( lowerColumnName ) )
310 {
311 fieldName = lowerColumnName;
312 }
313 if ( fieldName != null )
314 {
315 localFilter.append( "&sorter/field=" );
316 localFilter.append( fieldName );
317 localFilter.append( "&sorter/order=" );
318 localFilter.append( descending ? "DESC" : "ASC" );
319 validSortColumnNames++;
320 }
321 else
322 {
323
324 getLog().error(
325 "maven-changes-plugin: The configured value '" + lowerColumnName
326 + "' for sortColumnNames is not correct." );
327 }
328 }
329 }
330 if ( validSortColumnNames == 0 )
331 {
332
333 getLog().error(
334 "maven-changes-plugin: None of the configured sortColumnNames '" + sortColumnNames + "' are correct." );
335 }
336
337
338 return localFilter.toString();
339 }
340
341
342
343
344
345
346 public void doExecute()
347 throws Exception
348 {
349 try
350 {
351 HttpClient client = new HttpClient();
352
353
354 HttpClientParams clientParams = client.getParams();
355 clientParams.setBooleanParameter( HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true );
356
357 HttpState state = new HttpState();
358
359 HostConfiguration hc = new HostConfiguration();
360
361 client.setHostConfiguration( hc );
362
363 client.setState( state );
364
365 Map<String,String> urlMap = JiraHelper.getJiraUrlAndProjectId( project.getIssueManagement().getUrl() );
366
367 String jiraUrl = urlMap.get( "url" );
368 getLog().debug( "JIRA lives at: " + jiraUrl );
369
370 String jiraId = urlMap.get( "id" );
371
372 determineProxy( jiraUrl, client );
373
374 prepareBasicAuthentication( client );
375
376 boolean jiraAuthenticationSuccessful = false;
377 if ( isJiraAuthenticationConfigured() )
378 {
379 jiraAuthenticationSuccessful = doJiraAuthentication( client, jiraUrl );
380 }
381
382 if ( ( isJiraAuthenticationConfigured() && jiraAuthenticationSuccessful )
383 || !isJiraAuthenticationConfigured() )
384 {
385 if ( jiraId == null || jiraId.length() == 0 )
386 {
387 log.debug( "The JIRA URL " + project.getIssueManagement().getUrl()
388 + " doesn't include a pid, trying to extract it from JIRA." );
389 jiraId = JiraHelper.getPidFromJira( log, project.getIssueManagement().getUrl(), client );
390 }
391
392 if ( jiraId == null )
393 {
394 getLog().error( "The issue management URL in the POM does not include a pid,"
395 + " and it was not possible to extract it from the page at that URL." );
396 }
397 else
398 {
399
400 String fullURL = jiraUrl + "/secure/IssueNavigator.jspa?view=rss&pid=" + jiraId;
401
402 if ( getFixFor() != null )
403 {
404 fullURL += "&fixfor=" + getFixFor();
405 }
406
407 String createdFilter = createFilter();
408 if ( createdFilter.charAt( 0 ) != '&' )
409 {
410 fullURL += "&";
411 }
412 fullURL += createdFilter;
413
414 fullURL += ( "&tempMax=" + nbEntriesMax + "&reset=true&decorator=none" );
415
416 if ( log.isDebugEnabled() )
417 {
418 log.debug( "download jira issues from url " + fullURL );
419 }
420
421
422 download( client, fullURL );
423 }
424 }
425 }
426 catch ( Exception e )
427 {
428 if ( project.getIssueManagement() != null )
429 {
430 getLog().error( "Error accessing " + project.getIssueManagement().getUrl(), e );
431 }
432 else
433 {
434 getLog().error( "Error accessing mock project issues", e );
435 }
436 }
437 }
438
439
440
441
442
443
444 protected String getFixFor()
445 {
446 return null;
447 }
448
449
450
451
452
453
454 private void prepareBasicAuthentication( HttpClient client )
455 {
456 if ( ( webUser != null ) && ( webUser.length() > 0 ) )
457 {
458 client.getParams().setAuthenticationPreemptive( true );
459
460 Credentials defaultcreds = new UsernamePasswordCredentials( webUser, webPassword );
461
462 getLog().debug( "Using username: " + webUser + " for Basic Authentication." );
463
464 client.getState().setCredentials( new AuthScope( null, AuthScope.ANY_PORT, null, AuthScope.ANY_SCHEME ),
465 defaultcreds );
466 }
467 }
468
469
470
471
472
473
474
475
476
477
478 private boolean doJiraAuthentication( HttpClient client, final String jiraUrl )
479 {
480
481 String loginUrl = null;
482
483 StringBuffer loginLink = new StringBuffer( jiraUrl );
484
485 loginLink.append( "/login.jsp?os_destination=/secure/" );
486
487 try
488 {
489 loginLink.append( "&os_username=" ).append( URLEncoder.encode( jiraUser, UTF_8 ) );
490
491 String password = null;
492 if ( jiraPassword != null )
493 {
494 password = StringUtils.repeat( "*", jiraPassword.length() );
495 }
496 getLog().debug( "Login URL: " + loginLink + "&os_password=" + password );
497
498 loginLink.append( "&os_password=" ).append( URLEncoder.encode( jiraPassword, UTF_8 ) );
499
500 loginUrl = loginLink.toString();
501
502
503 GetMethod loginGet = new GetMethod( loginUrl );
504
505 client.executeMethod( loginGet );
506
507 if ( loginSucceeded( loginGet ) )
508 {
509 getLog().debug( "Successfully logged in into JIRA." );
510 return true;
511 }
512 else
513 {
514 getLog().warn( "Was unable to login into JIRA: wrong username and/or password." );
515 }
516 }
517 catch ( Exception e )
518 {
519 if ( getLog().isDebugEnabled() )
520 {
521 getLog().error( "Error trying to login into JIRA.", e );
522 }
523 else
524 {
525 getLog().error( "Error trying to login into JIRA. Cause is: " + e.getLocalizedMessage() );
526 }
527 }
528 return false;
529 }
530
531
532
533
534
535
536 private boolean isJiraAuthenticationConfigured()
537 {
538 return ( jiraUser != null ) && ( jiraUser.length() > 0 ) && ( jiraPassword != null );
539 }
540
541
542
543
544
545
546
547
548
549 private boolean loginSucceeded( GetMethod loginGet )
550 throws IOException
551 {
552 final String loginFailureResponse = "your username and password are incorrect";
553
554 return loginGet.getResponseBodyAsString().indexOf( loginFailureResponse ) == -1;
555 }
556
557
558
559
560
561
562 private void determineProxy( String jiraUrl, HttpClient client )
563 {
564
565 Proxy proxy = null;
566
567 String proxyHost = null;
568
569 int proxyPort = 0;
570
571 String proxyUser = null;
572
573 String proxyPass = null;
574
575 if ( project == null )
576 {
577 getLog().error( "No project set. No proxy info available." );
578
579 return;
580 }
581
582 if ( settings != null )
583 {
584 proxy = settings.getActiveProxy();
585 }
586
587 if ( proxy != null )
588 {
589
590 ProxyInfo proxyInfo = new ProxyInfo();
591 proxyInfo.setNonProxyHosts( proxy.getNonProxyHosts() );
592
593
594 URL url = null;
595 try
596 {
597 url = new URL( jiraUrl );
598 }
599 catch( MalformedURLException e )
600 {
601 getLog().error( "Invalid JIRA URL: " + jiraUrl + ". " + e.getMessage() );
602 }
603 String jiraHost = null;
604 if ( url != null )
605 {
606 jiraHost = url.getHost();
607 }
608
609
610
611
612
613 if ( JiraHelper.validateNonProxyHosts( proxyInfo, jiraHost ) )
614 {
615 return;
616 }
617
618 proxyHost = settings.getActiveProxy().getHost();
619
620 proxyPort = settings.getActiveProxy().getPort();
621
622 proxyUser = settings.getActiveProxy().getUsername();
623
624 proxyPass = settings.getActiveProxy().getPassword();
625
626 getLog().debug( proxyPass );
627 }
628
629 if ( proxyHost != null )
630 {
631 client.getHostConfiguration().setProxy( proxyHost, proxyPort );
632
633 getLog().debug( "Using proxy: " + proxyHost + " at port " + proxyPort );
634
635 if ( proxyUser != null )
636 {
637 getLog().debug( "Using proxy user: " + proxyUser );
638
639 client.getState().setProxyCredentials(
640 new AuthScope( null, AuthScope.ANY_PORT, null,
641 AuthScope.ANY_SCHEME ),
642 new UsernamePasswordCredentials( proxyUser, proxyPass ) );
643 }
644 }
645 }
646
647
648
649
650
651
652
653 private void download( final HttpClient cl, final String link )
654 {
655 try
656 {
657 GetMethod gm = new GetMethod( link );
658
659 getLog().info( "Downloading from JIRA at: " + link );
660
661 gm.setFollowRedirects( true );
662
663 cl.executeMethod( gm );
664
665 StatusLine sl = gm.getStatusLine();
666
667 if ( sl == null )
668 {
669 getLog().error( "Unknown error validating link: " + link );
670
671 return;
672 }
673
674
675 if ( gm.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY )
676 {
677 Header locationHeader = gm.getResponseHeader( "Location" );
678
679 if ( locationHeader == null )
680 {
681 getLog().warn( "Site sent redirect, but did not set Location header" );
682 }
683 else
684 {
685 String newLink = locationHeader.getValue();
686
687 getLog().debug( "Following redirect to " + newLink );
688
689 download( cl, newLink );
690 }
691 }
692
693 if ( gm.getStatusCode() == HttpStatus.SC_OK )
694 {
695 final InputStream responseBodyStream = gm.getResponseBodyAsStream();
696
697 if ( !output.getParentFile().exists() )
698 {
699 output.getParentFile().mkdirs();
700 }
701
702
703 OutputStream out = null;
704 try
705 {
706 out = new FileOutputStream( output );
707 IOUtil.copy( responseBodyStream, out );
708 }
709 finally
710 {
711 IOUtil.close( out );
712 IOUtil.close( responseBodyStream );
713 }
714
715 getLog().debug( "Downloading from JIRA was successful" );
716 }
717 else
718 {
719 getLog().warn( "Downloading from JIRA failed. Received: [" + gm.getStatusCode() + "]" );
720 }
721 }
722 catch ( HttpException e )
723 {
724 if ( getLog().isDebugEnabled() )
725 {
726 getLog().error( "Error downloading issues from JIRA:", e );
727 }
728 else
729 {
730 getLog().error( "Error downloading issues from JIRA url: " + e.getLocalizedMessage() );
731
732 }
733 }
734 catch ( IOException e )
735 {
736 if ( getLog().isDebugEnabled() )
737 {
738 getLog().error( "Error downloading issues from JIRA:", e );
739 }
740 else
741 {
742 getLog().error( "Error downloading issues from JIRA. Cause is " + e.getLocalizedMessage() );
743 }
744 }
745 }
746
747 public List<Issue> getIssueList()
748 throws MojoExecutionException
749 {
750 if ( output.isFile() )
751 {
752 JiraXML jira = new JiraXML( log, jiraDatePattern );
753 jira.parseXML( output );
754 getLog().info( "The JIRA version is '" + jira.getJiraVersion() + "'" );
755 return jira.getIssueList();
756 }
757 else
758 {
759 getLog().warn( "JIRA file " + output.getPath() + " doesn't exist." );
760 return Collections.emptyList();
761 }
762 }
763
764 public void setJiraDatePattern( String jiraDatePattern )
765 {
766 this.jiraDatePattern = jiraDatePattern;
767 }
768
769
770
771
772
773
774 public void setOutput( File thisOutput )
775 {
776 this.output = thisOutput;
777 }
778
779 public File getOutput()
780 {
781 return this.output;
782 }
783
784
785
786
787
788
789 public void setMavenProject( Object thisProject )
790 {
791 this.project = (MavenProject) thisProject;
792 }
793
794
795
796
797
798
799 public void setNbEntries( final int nbEntries )
800 {
801 nbEntriesMax = nbEntries;
802 }
803
804
805
806
807
808
809 public void setStatusIds( String thisStatusIds )
810 {
811 statusIds = thisStatusIds;
812 }
813
814
815
816
817
818
819 public void setPriorityIds( String thisPriorityIds )
820 {
821 priorityIds = thisPriorityIds;
822 }
823
824
825
826
827
828
829 public void setResolutionIds( String thisResolutionIds )
830 {
831 resolutionIds = thisResolutionIds;
832 }
833
834
835
836
837
838
839 public void setSortColumnNames( String thisSortColumnNames )
840 {
841 sortColumnNames = thisSortColumnNames;
842 }
843
844
845
846
847
848
849 public void setWebPassword( String thisWebPassword )
850 {
851 this.webPassword = thisWebPassword;
852 }
853
854
855
856
857
858
859 public void setWebUser( String thisWebUser )
860 {
861 this.webUser = thisWebUser;
862 }
863
864
865
866
867
868
869 public void setJiraPassword( final String thisJiraPassword )
870 {
871 this.jiraPassword = thisJiraPassword;
872 }
873
874
875
876
877
878
879 public void setJiraUser( String thisJiraUser )
880 {
881 this.jiraUser = thisJiraUser;
882 }
883
884
885
886
887
888
889 public void setFilter( String thisFilter )
890 {
891 this.filter = thisFilter;
892 }
893
894
895
896
897
898
899 public void setComponent( String theseComponents )
900 {
901 this.component = theseComponents;
902 }
903
904
905
906
907
908
909 public void setFixVersionIds( String theseFixVersionIds )
910 {
911 this.fixVersionIds = theseFixVersionIds;
912 }
913
914
915
916
917
918
919 public void setTypeIds( String theseTypeIds )
920 {
921 typeIds = theseTypeIds;
922 }
923
924 public void setLog( Log log )
925 {
926 this.log = log;
927 }
928
929 private Log getLog()
930 {
931 return log;
932 }
933
934 public void setSettings( Settings settings )
935 {
936 this.settings = settings;
937 }
938 }