Java tutorial
/** * Aptana Studio * Copyright (c) 2012 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.jira.core; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; import java.util.Map; import java.util.regex.Matcher; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.multipart.FilePart; import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; import org.apache.commons.httpclient.methods.multipart.Part; import org.eclipse.core.internal.preferences.Base64; import org.eclipse.core.runtime.IPath; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.SecurePreferencesFactory; import org.osgi.framework.Version; import com.aptana.core.logging.IdeLog; import com.aptana.core.util.EclipseUtil; import com.aptana.core.util.IOUtil; import com.aptana.core.util.StringUtil; import com.aptana.jetty.util.epl.ajax.JSON; /** * @author cwilliams * @author Michael Xia (mxia@appcelerator.com) */ public class JiraManager { /** * Authorization header name. Used for Basic Auth over HTTP/S. */ private static final String AUTHORIZATION_HEADER = "Authorization"; //$NON-NLS-1$ /** * User agent header name. Used to identify studio as the "client". */ private static final String USER_AGENT = "User-Agent"; //$NON-NLS-1$ /** * Content Type Accept header. Tells JIRA we can accept given content types. We default to JSON. */ private static final String ACCEPT_HEADER = "Accept"; //$NON-NLS-1$ private static final String ACCEPT_CONTENT_TYPES = "application/json"; //$NON-NLS-1$ private static final String CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$ /** * Secure Storage */ private static final String SECURE_PREF_NODE = "com.aptana.jira.core"; //$NON-NLS-1$ private static final String USERNAME = "username"; //$NON-NLS-1$ private static final String PASSWORD = "password"; //$NON-NLS-1$ /** * Project Keys for Aptana and Titanium */ static final String APTANA_STUDIO = "APSTUD"; //$NON-NLS-1$ static final String TITANIUM_COMMUNITY = "TC"; //$NON-NLS-1$ /** * Ticket field names. */ private static final String PARAM_ENVIRONMENT = "environment"; //$NON-NLS-1$ private static final String PARAM_VERSION = "versions"; //$NON-NLS-1$ // could override using setProjectInfo() private static String projectName = "Aptana Studio"; //$NON-NLS-1$ private static String projectKey = APTANA_STUDIO; /** * REST API URLs */ private static final String HOST_NAME = "jira.appcelerator.org"; //$NON-NLS-1$ private static final String HOST_URL = "https://" + HOST_NAME; //$NON-NLS-1$ private static final String REST_API_ENDPOINT = HOST_URL + "/rest/api/2/"; //$NON-NLS-1$ private JiraUser user; JiraManager() { loadCredentials(); } /** * @return the currently JIRA user, or null if there has not been a successful login attempt */ public JiraUser getUser() { return user; } /** * Logs into JIRA with a username and password. * * @param username * the username * @param password * the password * @throws JiraException */ public void login(String username, String password) throws JiraException { HttpURLConnection connection = null; try { connection = createConnection(getUserURL(username), username, password); int code = connection.getResponseCode(); if (code == HttpURLConnection.HTTP_OK) { this.user = new JiraUser(username, password); saveCredentials(); return; } if (code == HttpURLConnection.HTTP_UNAUTHORIZED || code == HttpURLConnection.HTTP_FORBIDDEN) { throw new JiraException(Messages.JiraManager_BadCredentialsErrMsg); } throw new JiraException(Messages.JiraManager_UnknownErrMsg); } catch (Exception e) { throw new JiraException(e.getMessage(), e); } finally { if (connection != null) { connection.disconnect(); } } } /** * The URL to get details on a user. * * @param username * @return */ protected String getUserURL(String username) { return REST_API_ENDPOINT + "user?username=" + username; //$NON-NLS-1$ } @SuppressWarnings("restriction") protected HttpURLConnection createConnection(String urlString, String username, String password) throws MalformedURLException, IOException { HttpURLConnection connection; URL url = new URL(urlString); connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty(USER_AGENT, getProjectVersion()); connection.setRequestProperty(ACCEPT_HEADER, ACCEPT_CONTENT_TYPES); connection.setRequestProperty(CONTENT_TYPE, ACCEPT_CONTENT_TYPES); connection.setUseCaches(false); connection.setAllowUserInteraction(false); String usernamePassword = username + ":" + password; //$NON-NLS-1$ connection.setRequestProperty(AUTHORIZATION_HEADER, "Basic " + new String(Base64.encode(usernamePassword.getBytes()))); //$NON-NLS-1$ return connection; } /** * Logs current user out of JIRA in Studio. */ public void logout() { ISecurePreferences prefs = getSecurePreferences(); try { prefs.remove(USERNAME); prefs.remove(PASSWORD); prefs.flush(); user = null; } catch (Exception e) { IdeLog.logError(JiraCorePlugin.getDefault(), "Failed to log out Jira user", e); //$NON-NLS-1$ } } /** * Creates a JIRA ticket. * * @param type * the issue type (bug, feature, or improvement) * @param priority * the issue's priority * @param severity * the issue's severity (Blocker, Major, Minor, Trivial, None) * @param summary * the summary of the ticket * @param description * the description of the ticket * @return the JIRA issue created * @throws JiraException * @throws IOException */ public JiraIssue createIssue(JiraIssueType type, JiraIssueSeverity severity, String summary, String description) throws JiraException, IOException { if (user == null) { throw new JiraException(Messages.JiraManager_ERR_NotLoggedIn); } HttpURLConnection connection = null; try { connection = createConnection(getCreateIssueURL(), user.getUsername(), user.getPassword()); connection.setRequestMethod("POST"); //$NON-NLS-1$ connection.setDoOutput(true); String severityJSON; String versionString; if ((TITANIUM_COMMUNITY.equals(projectKey) && type == JiraIssueType.IMPROVEMENT) || (!TITANIUM_COMMUNITY.equals(projectKey) && type != JiraIssueType.BUG)) { // Improvements in TC don't get Severities, nor do Story or Improvements in Aptana Studio! severityJSON = StringUtil.EMPTY; } else { severityJSON = severity.getParameterValue() + ",\n"; //$NON-NLS-1$ } // If we're submitting against TC, we can't do version, we need to stuff that into the Environment if (TITANIUM_COMMUNITY.equals(projectKey)) { versionString = MessageFormat.format("\"{0}\": \"{1}\"", PARAM_ENVIRONMENT, getProjectVersion()); //$NON-NLS-1$ } else { versionString = "\"" + PARAM_VERSION + "\": [{\"name\": \"" + getProjectVersion() + "\"}]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } // @formatter:off String data = "{\n" + //$NON-NLS-1$ " \"fields\": {\n" + //$NON-NLS-1$ " \"project\":\n" + //$NON-NLS-1$ " { \n" + //$NON-NLS-1$ " \"key\": \"" + projectKey + "\"\n" + //$NON-NLS-1$ //$NON-NLS-2$ " },\n" + //$NON-NLS-1$ " \"summary\": \"" + summary.replaceAll("\"", "'") + "\",\n" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ " \"description\": \"" //$NON-NLS-1$ + description.replaceAll("\"", "'").replaceAll("\n", Matcher.quoteReplacement("\\n")) + "\",\n" //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$//$NON-NLS-5$ + " \"issuetype\": {\n" + " \"name\": \"" + type.getParameterValue(projectKey) + "\"\n" + //$NON-NLS-3$ " },\n" + //$NON-NLS-1$ severityJSON + " " + versionString + "\n" + //$NON-NLS-1$ //$NON-NLS-2$ " }\n" + //$NON-NLS-1$ "}"; //$NON-NLS-1$ // @formatter:on OutputStream out = connection.getOutputStream(); IOUtil.write(out, data); out.close(); int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_CREATED) { String output = IOUtil.read(connection.getInputStream()); return createIssueFromJSON(output); } // failed to create the ticket // TODO Parse the response as JSON! throw new JiraException(IOUtil.read(connection.getErrorStream())); } catch (JiraException je) { throw je; } catch (Exception e) { throw new JiraException(e.getMessage(), e); } finally { if (connection != null) { connection.disconnect(); } } } /** * @param output * @return */ @SuppressWarnings("unchecked") protected JiraIssue createIssueFromJSON(String output) { Map<String, Object> map = (Map<String, Object>) JSON.parse(output); String issueKey = (String) map.get("key"); //$NON-NLS-1$ String issueId = (String) map.get("id"); //$NON-NLS-1$ String issueUrl = HOST_URL + "/browse/" + issueKey; //$NON-NLS-1$ return new JiraIssue(issueKey, issueId, issueUrl); } protected String getCreateIssueURL() { return REST_API_ENDPOINT + "issue"; //$NON-NLS-1$ } /** * Adds an attachment to a JIRA ticket. * * @param path * the path of the file to be attached * @param issue * the JIRA ticket * @throws JiraException */ public void addAttachment(IPath path, JiraIssue issue) throws JiraException { if (path == null || issue == null) { return; } if (user == null) { throw new JiraException(Messages.JiraManager_ERR_NotLoggedIn); } // Use Apache HTTPClient to POST the file HttpClient httpclient = new HttpClient(); UsernamePasswordCredentials creds = new UsernamePasswordCredentials(user.getUsername(), user.getPassword()); httpclient.getState().setCredentials(new AuthScope(HOST_NAME, 443), creds); httpclient.getParams().setAuthenticationPreemptive(true); PostMethod filePost = null; try { filePost = new PostMethod(createAttachmentURL(issue)); File file = path.toFile(); // MUST USE "file" AS THE NAME!!! Part[] parts = { new FilePart("file", file) }; //$NON-NLS-1$ filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams())); filePost.setContentChunked(true); filePost.setDoAuthentication(true); // Special header to tell JIRA not to do XSFR checking filePost.setRequestHeader("X-Atlassian-Token", "nocheck"); //$NON-NLS-1$ //$NON-NLS-2$ int responseCode = httpclient.executeMethod(filePost); if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_CREATED) { // TODO This is a JSON response that we should parse out "errorMessages" value(s) (its an array of // strings). throw new JiraException(filePost.getResponseBodyAsString()); } String json = filePost.getResponseBodyAsString(); IdeLog.logInfo(JiraCorePlugin.getDefault(), json); } catch (JiraException e) { throw e; } catch (Exception e) { throw new JiraException(e.getMessage(), e); } finally { if (filePost != null) { filePost.releaseConnection(); } } } protected String createAttachmentURL(JiraIssue issue) { return REST_API_ENDPOINT + "issue/" + issue.getName() + "/attachments"; //$NON-NLS-1$ //$NON-NLS-2$ } /** * Specifies the JIRA project to log the ticket against. By default Aptana Studio will be used. * * @param projectName * the name of the JIRA project (e.g. Aptana Studio) * @param projectKey * the key of the JIRA project (e.g. APSTUD for Aptana Studio) */ public static void setProjectInfo(String projectName, String projectKey) { if (!StringUtil.isEmpty(projectName)) { JiraManager.projectName = projectName; } if (!StringUtil.isEmpty(projectKey)) { JiraManager.projectKey = projectKey; } } private void loadCredentials() { ISecurePreferences prefs = getSecurePreferences(); try { String username = prefs.get(USERNAME, null); String password = prefs.get(PASSWORD, null); if (!StringUtil.isEmpty(username) && !StringUtil.isEmpty(password)) { user = new JiraUser(username, password); } } catch (Exception e) { IdeLog.logError(JiraCorePlugin.getDefault(), "Failed to load Jira user credentials", e); //$NON-NLS-1$ } } private void saveCredentials() { ISecurePreferences prefs = getSecurePreferences(); try { prefs.put(USERNAME, user.getUsername(), true /* encrypt */); prefs.put(PASSWORD, user.getPassword(), true /* encrypt */); prefs.flush(); } catch (Exception e) { IdeLog.logError(JiraCorePlugin.getDefault(), "Failed to save Jira user credentials", e); //$NON-NLS-1$ } } private String getProjectVersion() { String versionStr = EclipseUtil.getStudioVersion(); // we don't need the qualifier Version version = new Version(versionStr); return MessageFormat.format("{0} {1}.{2}.{3}", projectName, version.getMajor(), version.getMinor(), //$NON-NLS-1$ version.getMicro()); } protected ISecurePreferences getSecurePreferences() { return SecurePreferencesFactory.getDefault().node(SECURE_PREF_NODE); } }