All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugin.jira.RestJiraDownloader Maven / Gradle / Ivy

Go to download

Creates a release history for inclusion into the site and assists in generating an announcement mail.

There is a newer version: 2.12.1
Show newest version
package org.apache.maven.plugin.jira;

/*
 * 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.fasterxml.jackson.core.JsonFactory;
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.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.issues.Issue;

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.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Use the JIRA REST API to implement the download.
 *
 * This class assumes that the URL points to a copy of JIRA that implements the REST API.
 * A static function may be forthcoming in here to probe and see if a given URL supports it.
 *
 */
public class RestJiraDownloader extends AbstractJiraDownloader
{
    private List issueList;
    private JsonFactory jsonFactory;
    private SimpleDateFormat dateFormat;

    private List resolvedFixVersionIds;
    private List resolvedStatusIds;
    private List resolvedComponentIds;
    private List resolvedTypeIds;
    private List resolvedResolutionIds;
    private List resolvedPriorityIds;

    private String jiraProject;

    public static class NoRest extends Exception
    {
        public NoRest( )
        {
            // blank on purpose.
        }
        public NoRest( String message )
        {
            super( message );
        }
    }

    public RestJiraDownloader()
    {
        jsonFactory = new MappingJsonFactory(  );
        //2012-07-17T06:26:47.723-0500
        dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" );
        resolvedFixVersionIds = new ArrayList(  );
        resolvedStatusIds = new ArrayList(  );
        resolvedComponentIds = new ArrayList(  );
        resolvedTypeIds = new ArrayList(  );
        resolvedResolutionIds = new ArrayList(  );
        resolvedPriorityIds = new ArrayList(  );
    }

    public void doExecute()
        throws Exception
    {

        Map urlMap = JiraHelper.getJiraUrlAndProjectName( project.getIssueManagement().getUrl() );
        String jiraUrl = urlMap.get( "url" );
        jiraProject = urlMap.get( "project" );

        // This classloader juggling is a workaround for a classic Maven 2 class loader management bug.
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        try
        {
            Thread.currentThread().setContextClassLoader( WebClient.class.getClassLoader( ) );
            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." );
            }

            doSessionAuth( client );

            resolveIds( client, jiraProject );

            String jqlQuery = new JqlQueryBuilder( log ).urlEncode( false ).project( jiraProject ).fixVersion(
                getFixFor() ).fixVersionIds( resolvedFixVersionIds ).statusIds( resolvedStatusIds ).priorityIds(
                resolvedPriorityIds ).resolutionIds( resolvedResolutionIds ).components( resolvedComponentIds ).typeIds(
                resolvedTypeIds ).sortColumnNames( sortColumnNames ).build();

            StringWriter searchParamStringWriter = new StringWriter();
            JsonGenerator gen = jsonFactory.createGenerator( searchParamStringWriter );
            gen.writeStartObject();
            gen.writeStringField( "jql", jqlQuery );
            gen.writeNumberField( "maxResults", nbEntriesMax );
            gen.writeArrayFieldStart( "fields" );
            // Retrieve all fields. If that seems slow, we can reconsider.
            gen.writeString( "*all" );
            gen.writeEndArray();
            gen.writeEndObject();
            gen.close();
            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 );
            }

            JsonNode issueTree = getResponseTree( searchResponse );
            assert issueTree.isObject();
            JsonNode issuesNode = issueTree.get( "issues" );
            assert issuesNode.isArray();
            buildIssues( issuesNode, jiraUrl, jiraProject );
        }
        finally
        {
            Thread.currentThread().setContextClassLoader( ccl );
        }
    }

    private JsonNode getResponseTree( Response response )
        throws IOException
    {
        JsonParser jsonParser = jsonFactory.createJsonParser( (InputStream) response.getEntity() );
        return (JsonNode) jsonParser.readValueAsTree();
    }

    private void reportErrors( Response resp )
        throws IOException, MojoExecutionException
    {
        if ( MediaType.APPLICATION_JSON_TYPE.getType().equals( getResponseMediaType( resp ).getType() ) )
        {
            JsonNode errorTree = getResponseTree( resp );
            assert errorTree.isObject();
            JsonNode messages = errorTree.get( "errorMessages" );
            if ( messages != null )
            {
                for ( int mx = 0; mx < messages.size(); mx ++ )
                {
                    getLog().error( messages.get( mx ).asText() );
                }
            }
            else
            {
                JsonNode message = errorTree.get( "message" );
                if ( message != null )
                {
                    getLog().error( message.asText() );
                }
            }
        }
        throw new MojoExecutionException( String.format( "Failed to query issues; response %d", resp.getStatus() ) );
    }

    private void resolveIds( WebClient client, String jiraProject )
        throws IOException, MojoExecutionException, MojoFailureException
    {
        resolveList( resolvedComponentIds, client, "components",  component, "/rest/api/2/project/{key}/components",
                     jiraProject );
        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" );
    }

    private void resolveList( List targetList, WebClient client, String what, String input,
                              String listRestUrlPattern, String... listUrlArgs )
        throws IOException, MojoExecutionException, MojoFailureException
    {
        if ( input == null || input.length() == 0 )
        {
            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() != 200 )
        {
            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 ) );
        }
    }

    private String resolveOneItem( JsonNode items, String what, String nameOrId )
        throws IOException, MojoExecutionException, MojoFailureException
    {
        for ( int cx = 0; cx < items.size(); cx ++ )
        {
            JsonNode item = items.get( cx );
            if ( nameOrId.equals( item.get( "id" ).asText() ) )
            {
                return nameOrId;
            }
            else if ( nameOrId.equals( item.get( "name" ).asText() ) )
            {
                return item.get( "id" ).asText();
            }
        }
        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 )
    {
        issueList = new ArrayList(  );
        for ( int ix = 0; ix < issuesNode.size(); ix++ )
        {
            JsonNode issueNode = issuesNode.get( ix );
            assert issueNode.isObject();
            Issue issue = new Issue();
            JsonNode val;

            val = issueNode.get( "id" );
            if ( val != null )
            {
                issue.setId( val.asText() );
            }

            val = issueNode.get( "key" );
            if ( val != null )
            {
                issue.setKey( val.asText() );
                issue.setLink( String.format( "%s/browse/%s", jiraUrl, val.asText() ) );
            }

            // much of what we want is in here.
            JsonNode fieldsNode = issueNode.get( "fields" );

            val = fieldsNode.get( "assignee" );
            processAssignee( issue, val );

            val = fieldsNode.get( "created" );
            processCreated( issue, val );

            val = fieldsNode.get( "comment" );
            processComments( issue, val );

            val = fieldsNode.get( "components" );
            processComponents( issue, val );

            val = fieldsNode.get( "fixVersions" );
            processFixVersions( issue, val );

            val = fieldsNode.get( "issuetype" );
            processIssueType( issue, val );

            val = fieldsNode.get( "priority" );
            processPriority( issue, val );

            val = fieldsNode.get( "reporter" );
            processReporter( issue, val );

            val = fieldsNode.get( "resolution" );
            processResolution( issue, val );

            val = fieldsNode.get( "status" );
            processStatus( issue, val );

            val = fieldsNode.get( "summary" );
            if ( val != null )
            {
                issue.setSummary( val.asText() );
            }

            val = fieldsNode.get( "title" );
            if ( val != null )
            {
                issue.setTitle( val.asText() );
            }

            val = fieldsNode.get( "updated" );
            processUpdated( issue, val );

            val = fieldsNode.get( "versions" );
            processVersions( issue, val );

            issueList.add( issue );
        }
    }

    private void processVersions( Issue issue, JsonNode val )
    {
        StringBuilder sb = new StringBuilder( );
        if ( val != null )
        {
            for ( int vx = 0; vx < val.size(); vx ++ )
            {
                sb.append( val.get( vx ).get( "name" ).asText() );
                sb.append( ", " );
            }
        }
        if ( sb.length() > 0 )
        {
            // remove last ", "
            issue.setVersion( sb.substring( 0, sb.length() - 2 ) );
        }
    }

    private void processStatus( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            issue.setStatus( val.get( "name" ).asText() );
        }
    }

    private void processPriority( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            issue.setPriority( val.get( "name" ).asText() );
        }
    }

    private void processResolution( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            issue.setResolution( val.get( "name" ).asText() );
        }
    }

    private String getPerson( JsonNode val )
    {
        JsonNode nameNode = val.get( "displayName" );
        if ( nameNode == null )
        {
            nameNode = val.get( "name" );
        }
        if ( nameNode != null )
        {
            return nameNode.asText();
        }
        else
        {
            return null;
        }
    }

    private void processAssignee( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            String text = getPerson( val );
            if ( text != null )
            {
                issue.setAssignee( text );
            }
        }
    }

    private void processReporter( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            String text = getPerson( val );
            if ( text != null )
            {
                issue.setReporter( text );
            }
        }
    }

    private void processCreated( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            try
            {
                issue.setCreated( parseDate( val ) );
            }
            catch ( ParseException e )
            {
                getLog().warn( "Invalid created date " + val.asText() );
            }
        }
    }

    private void processUpdated( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            try
            {
                issue.setUpdated( parseDate( val ) );
            }
            catch ( ParseException e )
            {
                getLog().warn( "Invalid updated date " + val.asText() );
            }
        }
    }

    private Date parseDate( JsonNode val )
        throws ParseException
    {
        return dateFormat.parse( val.asText() );
    }

    private void processFixVersions( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            assert val.isArray();
            for ( int vx = 0; vx < val.size(); vx++ )
            {
                JsonNode fvNode = val.get( vx );
                issue.addFixVersion( fvNode.get( "name" ).asText() );
            }
        }
    }

    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();
            for ( int cx = 0; cx < val.size(); cx++ )
            {
                JsonNode cnode = val.get( cx );
                issue.addComponent( cnode.get( "name" ).asText() );
            }
        }
    }

    private void processIssueType( Issue issue, JsonNode val )
    {
        if ( val != null )
        {
            issue.setType( val.get( "name" ).asText() );
        }
    }

    private void doSessionAuth( WebClient client )
        throws IOException, MojoExecutionException, NoRest
    {
        /* if JiraUser is specified instead of WebUser, we need to make a session. */
        if ( jiraUser != null )
        {
            client.replacePath( "/rest/auth/1/session" );
            client.type( MediaType.APPLICATION_JSON_TYPE );
            StringWriter jsWriter = new StringWriter( );
            JsonGenerator gen = jsonFactory.createGenerator( jsWriter );
            gen.writeStartObject();
            gen.writeStringField( "username", jiraUser );
            gen.writeStringField( "password", jiraPassword );
            gen.writeEndObject();
            gen.close();
            Response authRes = client.post( jsWriter.toString() );
            if ( authRes.getStatus() != Response.Status.OK.getStatusCode() )
            {
                if ( authRes.getStatus() != 401 && authRes.getStatus() != 403 )
                {
                    // 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.",
                                                                 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(  ) );
        }

        HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();

        httpClientPolicy.setConnectionTimeout( 36000 );
        httpClientPolicy.setAllowChunking( false );
        httpClientPolicy.setReceiveTimeout( 32000 );

        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 );
            }
        }

        if ( webUser != null )
        {
            AuthorizationPolicy authPolicy = new AuthorizationPolicy();
            authPolicy.setAuthorizationType( "Basic" );
            authPolicy.setUserName( webUser );
            authPolicy.setPassword( webPassword );
            http.setAuthorization( authPolicy );
        }

        http.setClient( httpClientPolicy );
        return client;
    }

    public List getIssueList() throws MojoExecutionException
    {
        return issueList;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy