com.hpe.software.utils.Utils.java Source code

Java tutorial

Introduction

Here is the source code for com.hpe.software.utils.Utils.java

Source

/******************************************************************************* 
 * Copyright 2016 Hewlett Packard Enterprise Development LP
 * 
 * Licensed 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 com.hpe.software.utils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.eclipse.egit.github.core.Issue;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.service.IssueService;
import org.eclipse.egit.github.core.service.RepositoryService;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.hpe.software.agm.connector.AgmConnector;
import com.hpe.software.agm.connector.models.Audit;
import com.hpe.software.agm.connector.models.BacklogItem;
import com.hpe.software.agm.connector.models.Defect;
import com.hpe.software.agm.connector.models.Defects;
import com.hpe.software.agm.connector.models.Sprint;
import com.hpe.software.agm.connector.query.BuilderFactory;
import com.hpe.software.agm.connector.query.QueryBuilder;
import com.hpe.software.exception.AuthenticationException;
import com.hpe.software.exception.BadResponseException;
import com.hpe.software.exception.InvalidOperationException;
import com.hpe.software.exception.NotFoundException;
import com.hpe.software.github.client.GitHubClientWithProxy;
import com.hpe.software.producer.models.AgMChangeNotification;
import com.hpe.software.producer.models.IssueChangeNotification;

/**
 * General utility methods for AgM integrations
 * @author straif
 *
 */
public class Utils {

    private final static Logger log = LoggerFactory.getLogger(Utils.class);

    public static final String CREATE_ACTION = "create";

    protected static final String DEFECT_CREATION_TIME = "creation-time";
    public static final String DEFECT_DEV_COMMENTS = "dev-comments";

    protected static final String BLI_STATUS_NEW = "New";
    protected static final String BLI_STATUS_INPROGRESS = "In Progress";
    protected static final String BLI_STATUS_INTESTING = "In Testing";
    protected static final String BLI_STATUS_DONE = "Done";
    protected static final String DEFECT_STATUS_NEW = "New";
    protected static final String DEFECT_STATUS_AWAITING_DECISION = "Awaiting Decision";
    protected static final String DEFECT_STATUS_PLANNED = "Planned";
    protected static final String DEFECT_STATUS_CLOSED_NO_CHANGE = "Closed - No Change";
    protected static final String DEFECT_STATUS_OPEN = "Open";
    protected static final String DEFECT_STATUS_FIXED = "Fixed";
    protected static final String DEFECT_STATUS_CLOSED = "Closed";
    protected static final String DEFECT_STATUS_PROPOSE_CLOSE = "Propose Close";
    protected static final String DEFECT_STATUS_DEFERRED = "Deferred";
    protected static final String DEFECT_STATUS_DUPLICATE = "Duplicate";

    public static final String ISSUE_STATE_OPEN = "open";
    public static final String ISSUE_LABEL_BUG = "bug";

    public static final String AGM_SPRINT_TENSE_PAST = "PAST";
    public static final String AGM_SPRINT_TENSE_CURRENT = "CURRENT";
    public static final String AGM_SPRINT_TENSE_FUTURE = "FUTURE";

    /**
      * DateFormatter for agm date (without time)
      */
    static SimpleDateFormat agmDateFormatter = createAgmDateFormatter();

    /**
      * DateFormatter for agm date (with time)
      * only temporary until API is fixed
      */
    static SimpleDateFormat agmDateTimeFormatter = createAgmDatetimeFormatter();

    /**
      * DateFormatter for GitHub and public API datetimes
      */
    static SimpleDateFormat dateFormatter;

    /**
     * Return the dateFormatter for UTC dates
     */
    public synchronized static SimpleDateFormat getDateFormatter() {
        if (dateFormatter == null) {
            dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        }

        return dateFormatter;

    }

    /**
     * Return the dateFormatter for AgM dates without time
     */
    synchronized public static SimpleDateFormat createAgmDateFormatter() {
        SimpleDateFormat retValue = new SimpleDateFormat("yyyy-MM-dd");
        retValue.setTimeZone(TimeZone.getTimeZone("UTC"));

        return retValue;
    }

    /**
     * Return the dateFormatter for AgM dates with time
     */
    synchronized public static SimpleDateFormat createAgmDatetimeFormatter() {
        SimpleDateFormat retValue = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        retValue.setTimeZone(TimeZone.getTimeZone("UTC"));

        return retValue;
    }

    /**
     * Method that check if a RabbitMQ message got from an AgM producer
     * is a {@code Create} event for a defect
     * 
     * @param changeNotification object got from parsing the message from the message bus 
     * @param cache 
     * @return true if it's a defect Creation, false otherwise
     * @throws IOException 
     * @throws JsonProcessingException 
     */
    public static boolean isDefectCreateNotification(AgMChangeNotification changeNotification, LocalCache cache)
            throws JsonProcessingException, IOException {
        if (changeNotification.getAudits() == null) {
            return false;
        }
        for (Audit audit : changeNotification.getAudits()) {
            if (audit.getAction().equalsIgnoreCase(CREATE_ACTION)) {
                //mitigate any race condition: even if we detect a creation, check if we might have it in the cache
                Issue existingIssue = cache.checkforEntity(changeNotification.getRecord());
                if (existingIssue == null) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Find a defect with the given ID in AgM
     * @param id
     * @param agmConnector
     * @param conf
     * @return
     * @throws AuthenticationException 
     * @throws BadResponseException 
     * @throws NumberFormatException 
     * @throws InvalidOperationException 
     */
    public static Defect findDefectAgM(long id, AgmConnector agmConnector, Configuration conf)
            throws NumberFormatException, BadResponseException, AuthenticationException, InvalidOperationException {
        Defect retValue = null;

        String searchField = conf.getConfigValue(Configuration.Key.AGM_GITHUB_ID_CUSTOM_FIELD);
        String searchValue = buildGUID(id, conf.getConfigValue(Configuration.Key.AGM_GITHUB_ID_PREFIX));

        QueryBuilder queryBuilder = BuilderFactory.createQueryBuilder().fieldname(searchField).isEqual(searchValue);

        Defects defects = agmConnector.getDefects(
                Integer.parseInt(conf.getConfigValue(Configuration.Key.AGM_WORKSPACE_ID)), queryBuilder, null, -1,
                -1);

        //Sanity check
        if (defects != null) {
            if (defects.getTotalResults() == 1) {
                //seems to be a race condition: sometimes GetTotalResults is 1 but no defect received. 
                if (!defects.getData().isEmpty()) {
                    retValue = defects.getData().get(0);
                }
            } else if (defects.getTotalResults() > 1) {
                String ids = "";
                for (Defect defect : defects.getData()) {
                    ids += defect.getId() + ";";
                }
                log.error("Got more than one defect for GitHub ID " + searchValue + ": " + ids);
                log.error("Update only defect " + defects.getData().get(0).getId());

                retValue = defects.getData().get(0);
            }
        }
        return retValue;
    }

    /**
     * Build a GUID from the id and its prefix
     * @param id
     * @param prefix
     * @return
     */
    public static String buildGUID(long id, String prefix) {
        return prefix + id;
    }

    /**
     * Extracts the gitHubId from an AgM entity
     * @param entity
     * @param gitHubIdField
     * @return
     */
    public static String getGitHubId(BacklogItem entity, String gitHubIdField) {
        return (String) entity.getAdditionalProperties().get(gitHubIdField);
    }

    /**
     * Removed the prefix from the GUID again
     * @param id
     * @param prefix
     * @return
     */
    public static String extractIDFromGUID(String id, String prefix) {
        return id.substring(prefix.length(), id.length());
    }

    /**
     * Get a repository with the given name and org
     * @param organization
     * @param repositoryName
     * @param client
     * @return found repository
     * @throws IOException
     * @throws NotFoundException if not found
     */
    public static Repository getRepository(String organization, String repositoryName, GitHubClientWithProxy client)
            throws IOException, NotFoundException {
        Repository repository = null;

        //TODO: found no better way than to read all repos and then look for the requested one
        //maybe there is a better way
        RepositoryService service = new RepositoryService(client);

        try {
            List<Repository> repos = service.getOrgRepositories(organization);
            for (Repository repo : repos) {
                if (repo.getName().equals(repositoryName)) {
                    repository = repo;
                    break;
                }
            }
        } catch (org.eclipse.egit.github.core.client.RequestException e) {
            log.debug("Can't access organization " + organization + ". Try private repositories...");
            log.debug(e.toString());
        }

        if (repository == null) {
            //check for private repository
            List<Repository> repos = service.getRepositories();
            for (Repository repo : repos) {
                if (repo.getName().equals(repositoryName)) {
                    repository = repo;
                    break;
                }
            }

            if (repository == null) {
                throw new NotFoundException("Repository " + repositoryName + " not found.");
            }
        }
        return repository;
    }

    /**
     * Checks the events for the user who done a change
     * @param issueChangeNotification
     * @param user
     * @return
     */
    public static boolean isDoneByUser(IssueChangeNotification issueChangeNotification, String user) {
        boolean changeByUser = false;
        boolean changeByOtherUser = false;

        //if there is at least one event from another user, we return false
        for (int i = 0; i < issueChangeNotification.getEvents().size(); i++) {
            if (user.equals(issueChangeNotification.getEvents().get(i).getActor().getLogin())) {
                changeByUser = true;
            } else {
                changeByOtherUser = true;
            }
        }

        return changeByUser && !changeByOtherUser;
    }

    /**
     * Connects to AgM
     * @param conf
     * @param agmConnector
     * @throws AuthenticationException
     * @throws BadResponseException
     */
    public static LocalDateTime connectToAgM(Configuration conf, AgmConnector agmConnector)
            throws AuthenticationException, BadResponseException {
        int expiresIn;
        expiresIn = agmConnector.connect(conf.getConfigValue(Configuration.Key.AGM_CLIENT_ID),
                conf.getConfigValue(Configuration.Key.AGM_CLIENT_SECRET));
        LocalDateTime retValue = LocalDateTime.now().plusSeconds(expiresIn);
        log.info("Connected to AgM; Connection token will expire at: " + retValue);

        return retValue;
    }

    /**
     * Checks if authentication will expire
     * @param conf
     * @param agmConnector
     * @throws AuthenticationException
     * @throws BadResponseException
     */
    public static LocalDateTime checkForExpiration(LocalDateTime expires, Configuration conf,
            AgmConnector agmConnector) throws AuthenticationException, BadResponseException {
        LocalDateTime retValue = expires;

        if (LocalDateTime.now().isAfter(retValue)) {
            retValue = Utils.connectToAgM(conf, agmConnector);
        }

        return retValue;
    }

    /**
     * Get last saved date for the given tenantId
     * @param tenantId
     * @return
     * @throws IOException 
     * @throws JsonProcessingException 
     */
    public static String getLastUpdateDate(String tenantId) throws JsonProcessingException, IOException {
        //default is current date in UTC
        String retValue = convertToDateString(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime());

        //try to read from a simple file in the current directory
        Path sinceFile = getLastUpdateFilename(tenantId);
        if (sinceFile.toFile().exists()) {
            try {
                retValue = Files.readAllLines(sinceFile, Charset.forName("UTF-8")).get(0);
            } catch (Exception ex) {
                log.warn("Couldn't read " + sinceFile.toString() + ".");
                log.warn(ex.toString());
            }
        }

        return retValue;
    }

    /**
     * create a filename out of the tenantId 
     * @param tenantId
     * @return
     * @throws IOException 
     * @throws JsonProcessingException 
     */
    protected static Path getLastUpdateFilename(String tenantId) throws JsonProcessingException, IOException {
        return FileSystems.getDefault().getPath(Configuration.getDataPath() + tenantId + ".txt");
    }

    /**
     * Converts a Date to datetime string 
     * @param dateToConvert
     * @return
     */
    public static String convertToDateString(Date dateToConvert) {
        return Utils.getDateFormatter().format(dateToConvert);
    }

    /**
     * Converts a datetime string to a Date
     * @param dateToConvert
     * @return
     * @throws ParseException
     */
    public static Date convertFromDate(String dateToConvert) throws ParseException {
        return Utils.getDateFormatter().parse(dateToConvert);
    }

    /**
     * Converts to a Date from an AgM Date string
     * @param dateToConvert
     * @return
     * @throws ParseException 
     */
    public static Date convertFromAgMDate(String dateToConvert) throws ParseException {
        Date retValue = agmDateFormatter.parse(dateToConvert);
        agmDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));

        return retValue;
    }

    /**
     * Converts a Date to an AgM Date string
     * @param dateToConvert
     * @return
     */
    public static String convertToAgMDate(Date dateToConvert) {
        return agmDateFormatter.format(dateToConvert);
    }

    /**
     * Converts to a Date from an AgM Date string with time
     * @param datetimeToConvert
     * @return
     * @throws ParseException 
     */
    public static Date convertFromAgMDatetime(String datetimeToConvert) throws ParseException {
        Date retValue = agmDateTimeFormatter.parse(datetimeToConvert);
        agmDateTimeFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));

        return retValue;
    }

    /**
     * Sets the date until the tenant was checked for changes the last time
     * @param tenantId
     * @param nextSinceDate
     * @param addSecond true if a second shall be added to this date
     * @return
     * @throws IOException 
     * @throws ParseException 
     */
    public static String setLastUpdateDate(String tenantId, String nextSinceDate, boolean addSecond)
            throws IOException, ParseException {
        String retValue = nextSinceDate;

        //simple file in the current directory
        Path sinceFile = getLastUpdateFilename(tenantId);

        if (addSecond) {
            Calendar latestCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
            latestCalendar.setTime(Utils.convertFromDate(nextSinceDate));
            latestCalendar.add(Calendar.SECOND, 1);
            retValue = Utils.convertToDateString(latestCalendar.getTime());
        }

        writeLastReadDate(retValue, sinceFile);

        return retValue;
    }

    /**
     * Simple helper to write the given String into a file
     * @param sinceDate
     * @param sinceFile
     * @throws IOException
     */
    public static void writeLastReadDate(String sinceDate, Path sinceFile) throws IOException {
        ArrayList<String> lines = new ArrayList<String>();
        lines.add(sinceDate);
        Files.write(sinceFile, lines, Charset.forName("UTF-8"));
    }

    /**
     * Calculate sprints tense based on current date
     * @throws ParseException 
     */
    public static String calculateSprintTense(Sprint sprintEntity) throws ParseException {
        String retValue = "";
        Date startDate = Utils.convertFromAgMDate(sprintEntity.getStartDate());
        Date endDate = Utils.convertFromAgMDate(sprintEntity.getEndDate());
        Date now = new Date();

        if (now.getTime() < startDate.getTime()) {
            retValue = Utils.AGM_SPRINT_TENSE_FUTURE;
        } else if (now.getTime() > endDate.getTime()) {
            retValue = Utils.AGM_SPRINT_TENSE_PAST;
        } else {
            retValue = Utils.AGM_SPRINT_TENSE_CURRENT;
        }

        return retValue;
    }

    /**
     * Generates an URL which can be used to directly jump to an userstory or defect in AgM SaaS 
     * @param serverUrl
     * @param domain
     * @param project
     * @param tenantId
     * @param workspaceId
     * @param backlogItem
     * @return
     */
    public static String generateEntityUrl(String serverUrl, String domain, String project, String tenantId,
            Integer workspaceId, BacklogItem backlogItem) {
        StringBuilder retValue = new StringBuilder();

        retValue.append(serverUrl);
        retValue.append("/agm/webui/alm/");
        retValue.append(domain);
        retValue.append("/");
        retValue.append(project);
        retValue.append("/apm?TENANTID=");
        retValue.append(tenantId);
        retValue.append("#product/backlog_items/shared.update");
        retValue.append(";entityTypeName=");

        if (backlogItem instanceof Defect) {
            retValue.append("defect");
        } else {
            retValue.append("requirement");
        }

        retValue.append(";productGroupId=");
        retValue.append(workspaceId.toString());
        retValue.append(";entityId=");
        retValue.append(backlogItem.getInternalId().toString());

        //this will request a login if needed
        retValue.append("&login-form-required=y");

        return retValue.toString();
    }

    /**
     * Check for an GitHub issue by name since a given date
     * @param repository
     * @param name
     * @param client
     * @param label
     * @param since UTC date since which is searched for an issue
     * @return
     * @throws IOException
     */
    public static Issue findIssueGitHub(Repository repository, String name, GitHubClientWithProxy client,
            String label, String since) throws IOException {
        Issue foundIssue = null;
        IssueService issueService = new IssueService(client);

        Map<String, String> filterData = new HashMap<String, String>();
        filterData.put("filter", "all");
        filterData.put("labels", label);
        filterData.put("state", "all");
        filterData.put("sort", "updated");
        filterData.put("direction", "asc");
        filterData.put("since", since);

        List<Issue> issues = issueService.getIssues(repository, filterData);
        for (Issue issue : issues) {
            if (issue.getTitle().equals(name)) {
                foundIssue = issue;
                break;
            }
        }

        return foundIssue;
    }
}