Java tutorial
/******************************************************************************* * 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; } }