ComponentRest.java

/*
 * Copyright (c) 2002-2020, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.lutecetools.web.rs;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.paris.lutece.plugins.lutecetools.business.Component;
import fr.paris.lutece.plugins.lutecetools.service.JiraService;
import fr.paris.lutece.plugins.lutecetools.service.MavenRepoService;
import fr.paris.lutece.plugins.lutecetools.service.SonarService;
import fr.paris.lutece.plugins.rest.service.RestConstants;
import fr.paris.lutece.plugins.rest.util.json.JSONUtil;
import fr.paris.lutece.plugins.rest.util.xml.XMLUtil;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.util.xml.XmlUtil;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import fr.paris.lutece.portal.service.util.AppLogService;

/**
 * Page resource
 */
@Path( RestConstants.BASE_PATH + Constants.PATH_PLUGIN )
public class ComponentRest
{
    private static final String KEY_COMPONENTS = "components";
    private static final String KEY_COMPONENT = "component";
    private static final String KEY_ID = "artifact_id";
    private static final String KEY_VERSION = "version";
    private static final String KEY_CORE_VERSION = "core_version";
    private static final String KEY_PARENT_POM_VERSION = "parent_pom_version";
    private static final String KEY_SNAPSHOT_VERSION = "snapshot_version";
    private static final String KEY_SNAPSHOT_CORE_VERSION = "snapshot_core_version";
    private static final String KEY_SNAPSHOT_PARENT_POM_VERSION = "snapshot_parent_pom_version";
    private static final String KEY_SONAR_NB_LINES = "sonar_nb_lines";
    private static final String KEY_SONAR_RCI = "sonar_rci";
    private static final String KEY_JIRA_CODE = "jira_code";
    private static final String KEY_JIRA_ROADMAP_URL = "jira_roadmap_url";
    private static final String KEY_JIRA_CURRENT_VERSION_CLOSED_ISSUES = "jira_current_version_closed_issues";
    private static final String KEY_JIRA_CURRENT_VERSION_OPENED_ISSUES = "jira_current_version_opened_issues";
    private static final String KEY_SCM_URL = "scm_url";
    private static final String KEY_SCM_SNAPSHOT_URL = "scm_snapshot_url";
    private static final String KEY_SCM_CONNECTION = "scm_connection";
    private static final String KEY_SCM_DEVELOPER_CONNECTION = "scm_developer_connection";
    private static final String KEY_KEYWORDS = "keywords";
    private static final String KEY_KEYWORD = "keyword";
    private static final String KEY_INTRODUCTION = "description";
    private static final String KEY_METADATA = "metadata";
    private static final String KEY_LANG = "lang";

    private static final ObjectMapper _mapper = new ObjectMapper( );

    @GET
    @Path( Constants.PATH_COMPONENT + Constants.PATH_ALL )
    public Response getComponents( @HeaderParam( HttpHeaders.ACCEPT ) String accept, @QueryParam( Constants.PARAMETER_FORMAT ) String format )
    {
        String entity;
        String mediaType;

        if ( ( ( accept != null ) && accept.contains( MediaType.APPLICATION_JSON ) ) || ( ( format != null ) && format.equals( Constants.MEDIA_TYPE_JSON ) ) )
        {
            entity = getComponentsJson( );
            mediaType = MediaType.APPLICATION_JSON;
        }
        else
        {
            entity = getComponentsXml( );
            mediaType = MediaType.APPLICATION_XML;
        }

        return Response.ok( entity, mediaType ).build( );
    }

    /**
     * Gets all components list in XML format
     * 
     * @return The list
     */
    public String getComponentsXml( )
    {
        StringBuffer sbXML = new StringBuffer( XmlUtil.getXmlHeader( ) );
        XmlUtil.beginElement( sbXML, KEY_COMPONENTS );

        for ( String strArtifactId : MavenRepoService.instance( ).getComponentsListFromRepository( ) )
        {
            XmlUtil.addElement( sbXML, KEY_ID, strArtifactId );
        }

        XmlUtil.endElement( sbXML, KEY_COMPONENTS );

        return sbXML.toString( );
    }

    /**
     * Gets all components list in JSON format
     * 
     * @return The list
     */
    public String getComponentsJson( )
    {
        JsonNode json = _mapper.createObjectNode( );
        ArrayNode jsonComponents = _mapper.createArrayNode( );

        for ( String strArtifactId : MavenRepoService.instance( ).getComponentsListFromRepository( ) )
        {
            JsonNode jsonComponent = _mapper.createObjectNode( );
            ( (ObjectNode) jsonComponent ).put( KEY_ID, strArtifactId );
            jsonComponents.add( jsonComponent );
        }

        ( (ObjectNode) json ).set( KEY_COMPONENTS, jsonComponents );

        return json.toString( );
    }

    /**
     * Gets a component by its artifact ID
     * 
     * @param strArtifactId
     * @param accept
     *            The Accept header parameter
     * @param format
     *            The format
     * @param bCache
     *            false if the component informations must be recalculated
     * @param strType
     *            the compoent type(lutece-plugin,lutece-site,...)
     * @return The response
     * @throws IOException
     *             if an error occurs
     */
    @GET
    @Path( Constants.PATH_COMPONENT + "/{" + Constants.PATH_ID + "}" )
    public Response getComponent( @PathParam( Constants.PATH_ID ) String strArtifactId, @HeaderParam( HttpHeaders.ACCEPT ) String accept,
            @QueryParam( Constants.PARAMETER_FORMAT ) String format, @QueryParam( Constants.PARAMETER_CACHE ) Boolean bCache,
            @QueryParam( Constants.PARAMETER_TYPE ) String strType ) throws IOException
    {
        String entity;
        String mediaType;
        
        boolean cache = true;
        if ( bCache != null )
        {
            cache = bCache;
        }

        if ( ( ( accept != null ) && accept.contains( MediaType.APPLICATION_JSON ) ) || ( ( format != null ) && format.equals( Constants.MEDIA_TYPE_JSON ) ) )
        {
            entity = getComponentJson( strArtifactId, cache, strType );
            mediaType = MediaType.APPLICATION_JSON;
        }
        else
        {
            entity = getComponentXml( strArtifactId, cache, strType );
            mediaType = MediaType.APPLICATION_XML;
        }

        return Response.ok( entity, mediaType ).build( );
    }

    /**
     * Gets a component in XML format
     * 
     * @param strArtifactId
     *            The artifact ID
     * @param bCache
     *            false if the component informations must be recalculated
     * @param strType
     *            the compoent type(lutece-plugin,lutece-site,...)
     * @return The XML output
     */
    public String getComponentXml( String strArtifactId, boolean bCache, String strType )
    {
        StringBuffer sbXML = new StringBuffer( );

        try
        {
            Component component = MavenRepoService.instance( ).getComponent( strArtifactId, true, !bCache, strType );

            if ( component != null )
            {
                sbXML.append( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" );
                addComponentXml( sbXML, component );
            }
        }
        catch( NumberFormatException e )
        {
            sbXML.append( XMLUtil.formatError( "Invalid component number", 3 ) );
        }
        catch( Exception e )
        {
            sbXML.append( XMLUtil.formatError( "Component not found", 1 ) );
        }

        return sbXML.toString( );
    }

    /**
     * Gets a component in JSON format
     * 
     * @param strArtifactId
     *            The artifact ID
     * @param bCache
     *            false if the component informations must be recalculated
     * @param strType
     *            the compoent type(lutece-plugin,lutece-site,...)
     * @return The JSON output
     */
    public String getComponentJson( String strArtifactId, boolean bCache, String strType )
    {
        JsonNode json = _mapper.createObjectNode( );
        String strJson = "";

        try
        {

            Component component = MavenRepoService.instance( ).getComponent( strArtifactId, true, !bCache, strType );

            if ( component != null )
            {
                // Jackson automatic serialization to allow automatic deserialization
                addComponentJsonAuto( json, component );
                strJson = json.toString( );
            }
        }
        catch( NumberFormatException e )
        {
            strJson = JSONUtil.formatError( "Invalid component number", 3 );
        }
        catch( Exception e )
        {
            strJson = JSONUtil.formatError( "Component not found", 1 );
        }

        return strJson;
    }

    /**
     * Write a component into a buffer
     * 
     * @param sbXML
     *            The buffer
     * @param component
     *            The component
     */
    private void addComponentXml( StringBuffer sbXML, Component component )
    {
        XmlUtil.beginElement( sbXML, KEY_COMPONENT );
        XmlUtil.addElement( sbXML, KEY_ID, component.getArtifactId( ) );
        XmlUtil.addElement( sbXML, KEY_VERSION, component.getVersion( ) );
        XmlUtil.addElement( sbXML, KEY_CORE_VERSION, component.get( Component.CORE_VERSION ) );
        XmlUtil.addElement( sbXML, KEY_PARENT_POM_VERSION, component.get( Component.PARENT_POM_VERSION ) );
        XmlUtil.addElement( sbXML, KEY_SNAPSHOT_VERSION, component.get( Component.SNAPSHOT_VERSION ) );
        XmlUtil.addElement( sbXML, KEY_SNAPSHOT_CORE_VERSION, component.get( Component.SNAPSHOT_CORE_VERSION ) );
        XmlUtil.addElement( sbXML, KEY_SNAPSHOT_PARENT_POM_VERSION, component.get( Component.SNAPSHOT_PARENT_POM_VERSION ) );
        XmlUtil.addElement( sbXML, KEY_SONAR_NB_LINES, component.get( SonarService.SONAR_NB_LINES ) );
        XmlUtil.addElement( sbXML, KEY_SONAR_RCI, component.get( SonarService.SONAR_RCI ) );
        XmlUtil.addElement( sbXML, KEY_JIRA_CODE, component.get( Component.JIRA_KEY ) );
        XmlUtil.addElement( sbXML, KEY_JIRA_ROADMAP_URL, "https://dev.lutece.paris.fr/jira/projects/" + component.get( Component.JIRA_KEY )
                + "/?selectedTab=com.atlassian.jira.jira-projects-plugin:roadmap-panel" );
        XmlUtil.addElement( sbXML, KEY_JIRA_CURRENT_VERSION_CLOSED_ISSUES,
                component.getInt( JiraService.JIRA_ISSUES_COUNT ) - component.getInt( JiraService.JIRA_UNRESOLVED_ISSUES_COUNT ) );
        XmlUtil.addElement( sbXML, KEY_JIRA_CURRENT_VERSION_OPENED_ISSUES, component.getInt( JiraService.JIRA_UNRESOLVED_ISSUES_COUNT ) );
        XmlUtil.addElement( sbXML, KEY_SCM_URL, component.get( Component.SCM_URL ) );
        XmlUtil.addElement( sbXML, KEY_SCM_SNAPSHOT_URL, component.get( Component.SNAPSHOT_SCM_URL ) );
        XmlUtil.addElement( sbXML, KEY_SCM_CONNECTION, component.get( Component.SCM_CONNECTION ) );
        XmlUtil.addElement( sbXML, KEY_SCM_DEVELOPER_CONNECTION, component.get( Component.SCM_DEVELOPER_CONNECTION ) );

        List<Locale> locales = I18nService.getAdminAvailableLocales( );
        for ( Locale locale : locales )
        {
            Map<String, String> attributeList = new HashMap<>( );
            attributeList.put( KEY_LANG, locale.getLanguage( ) );

            XmlUtil.beginElement( sbXML, KEY_METADATA, attributeList );
            XmlUtil.addElementHtml( sbXML, KEY_INTRODUCTION, ( component.get( Component.SITE_INTRODUCTION + "_" + locale.getLanguage( ) ) == null ? ""
                    : component.get( Component.SITE_INTRODUCTION + "_" + locale.getLanguage( ) ) ) );

            Set<String> keywords = (HashSet<String>) component.getObject( Component.SITE_KEYWORDS + "_" + locale.getLanguage( ) );
            if ( keywords != null )
            {
                XmlUtil.beginElement( sbXML, KEY_KEYWORDS );
                for ( String keyword : keywords )
                {
                    XmlUtil.addElement( sbXML, KEY_KEYWORD, keyword );
                }
                XmlUtil.endElement( sbXML, KEY_KEYWORDS );
            }

            XmlUtil.endElement( sbXML, KEY_METADATA );
        }

        XmlUtil.endElement( sbXML, KEY_COMPONENT );
    }

    /**
     * Write a component into a JSON Object
     * 
     * @param json
     *            The JSON Object
     * @param component
     *            The component
     */
    private void addComponentJsonAuto( JsonNode json, Component component )
    {
        try
        {

            String strJson = _mapper.writeValueAsString( component );

            JsonNode jsonComponent = null;

            try
            {
                jsonComponent = _mapper.readTree( strJson );
            }
            catch( IOException e )
            {
                AppLogService.error( "ERROR serializing component", e );
            }

            ( (ObjectNode) json ).set( KEY_COMPONENT, jsonComponent );

        }
        catch( JsonProcessingException e )
        {
            AppLogService.error( "ERROR serializing component", e );
        }

    }

}