diff options
author | spingel | 2006-08-29 20:01:49 +0000 |
---|---|---|
committer | spingel | 2006-08-29 20:01:49 +0000 |
commit | 053b7ad1676c001e694dbeb21ae826a59def1f08 (patch) | |
tree | 61e4ae9c5fbd7f428881e7a21efeb3e9c27fc6fc /org.eclipse.mylyn.trac.core | |
parent | ba276227c0635484a6f9fa29c9a971ad3f4c13d8 (diff) | |
download | org.eclipse.mylyn.tasks-053b7ad1676c001e694dbeb21ae826a59def1f08.tar.gz org.eclipse.mylyn.tasks-053b7ad1676c001e694dbeb21ae826a59def1f08.tar.xz org.eclipse.mylyn.tasks-053b7ad1676c001e694dbeb21ae826a59def1f08.zip |
Progress on: 154876: extract trac.core plug-in from trac.ui
https://bugs.eclipse.org/bugs/show_bug.cgi?id=154876
Diffstat (limited to 'org.eclipse.mylyn.trac.core')
31 files changed, 3065 insertions, 3 deletions
diff --git a/org.eclipse.mylyn.trac.core/META-INF/MANIFEST.MF b/org.eclipse.mylyn.trac.core/META-INF/MANIFEST.MF index b685c1630..992121a74 100644 --- a/org.eclipse.mylyn.trac.core/META-INF/MANIFEST.MF +++ b/org.eclipse.mylyn.trac.core/META-INF/MANIFEST.MF @@ -10,4 +10,7 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.mylar.tasks.core, org.eclipse.search Eclipse-LazyStart: true -Export-Package: org.eclipse.mylar.trac.core.internal +Export-Package: org.eclipse.mylar.internal.trac.core, + org.eclipse.mylar.internal.trac.core.model, + org.eclipse.mylar.internal.trac.core.util +Bundle-Activator: org.eclipse.mylar.internal.trac.core.TracCorePlugin diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/AbstractTracClient.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/AbstractTracClient.java new file mode 100644 index 000000000..345be816a --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/AbstractTracClient.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.net.Proxy; +import java.net.URL; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylar.internal.trac.core.model.TracComponent; +import org.eclipse.mylar.internal.trac.core.model.TracMilestone; +import org.eclipse.mylar.internal.trac.core.model.TracPriority; +import org.eclipse.mylar.internal.trac.core.model.TracSeverity; +import org.eclipse.mylar.internal.trac.core.model.TracTicketResolution; +import org.eclipse.mylar.internal.trac.core.model.TracTicketStatus; +import org.eclipse.mylar.internal.trac.core.model.TracTicketType; +import org.eclipse.mylar.internal.trac.core.model.TracVersion; + +/** + * @author Steffen Pingel + */ +public abstract class AbstractTracClient implements ITracClient { + + protected String username; + + protected String password; + + protected URL repositoryUrl; + + protected Version version; + + protected TracClientData data; + + protected Proxy proxy; + + public AbstractTracClient(URL repositoryUrl, Version version, String username, String password) { + this.repositoryUrl = repositoryUrl; + this.version = version; + this.username = username; + this.password = password; + + this.data = new TracClientData(); + } + + public Version getVersion() { + return version; + } + + protected boolean hasAuthenticationCredentials() { + return username != null && username.length() > 0; + } + + + public TracComponent[] getComponents() { + return (data.components != null) ? data.components.toArray(new TracComponent[0]) : null; + } + + public TracMilestone[] getMilestones() { + return (data.milestones != null) ? data.milestones.toArray(new TracMilestone[0]) : null; + } + + public TracPriority[] getPriorities() { + return (data.priorities != null) ? data.priorities.toArray(new TracPriority[0]) : null; + } + + public TracSeverity[] getSeverities() { + return (data.severities != null) ? data.severities.toArray(new TracSeverity[0]) : null; + } + + public TracTicketResolution[] getTicketResolutions() { + return (data.ticketResolutions != null) ? data.ticketResolutions.toArray(new TracTicketResolution[0]) : null; + } + + public TracTicketStatus[] getTicketStatus() { + return (data.ticketStatus != null) ? data.ticketStatus.toArray(new TracTicketStatus[0]) : null; + } + + public TracTicketType[] getTicketTypes() { + return (data.ticketTypes != null) ? data.ticketTypes.toArray(new TracTicketType[0]) : null; + } + + public TracVersion[] getVersions() { + return (data.versions != null) ? data.versions.toArray(new TracVersion[0]) : null; + } + + public void updateAttributes(IProgressMonitor monitor, boolean force) throws TracException { + if (data.lastUpdate == 0 || force) { + updateAttributes(monitor); + data.lastUpdate = System.currentTimeMillis(); + } + } + + public abstract void updateAttributes(IProgressMonitor monitor) throws TracException; + + public void setData(TracClientData data) { + this.data = data; + } + + public String[] getDefaultTicketResolutions() { + return new String[] { "fixed", "invalid", "wontfix", "duplicate", "worksforme" }; + } + + public String[] getDefaultTicketActions(String status) { + if ("new".equals(status)) { + return new String[] { "leave", "resolve", "reassign", "accept" }; + } else if ("assigned".equals(status)) { + return new String[] { "leave", "resolve", "reassign" }; + } else if ("reopened".equals(status)) { + return new String[] { "leave", "resolve", "reassign" }; + } else if ("closed".equals(status)) { + return new String[] { "leave", "reopen" }; + } + return null; + } + + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + public Proxy getProxy() { + return proxy; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/ITracClient.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/ITracClient.java new file mode 100644 index 000000000..99e825c49 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/ITracClient.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.net.Proxy; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylar.internal.trac.core.model.TracComponent; +import org.eclipse.mylar.internal.trac.core.model.TracMilestone; +import org.eclipse.mylar.internal.trac.core.model.TracPriority; +import org.eclipse.mylar.internal.trac.core.model.TracSearch; +import org.eclipse.mylar.internal.trac.core.model.TracSeverity; +import org.eclipse.mylar.internal.trac.core.model.TracTicket; +import org.eclipse.mylar.internal.trac.core.model.TracTicketResolution; +import org.eclipse.mylar.internal.trac.core.model.TracTicketStatus; +import org.eclipse.mylar.internal.trac.core.model.TracTicketType; +import org.eclipse.mylar.internal.trac.core.model.TracVersion; + +/** + * Defines the requirements for classes that provide remote access to Trac + * repositories. + * + * @author Steffen Pingel + */ +public interface ITracClient { + + public enum Version { + TRAC_0_9, XML_RPC; + + public static Version fromVersion(String version) { + try { + return Version.valueOf(version); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public String toString() { + switch (this) { + case TRAC_0_9: + return "Trac 0.9 and later"; + case XML_RPC: + return "XML-RPC Plugin (Rev. " + TracXmlRpcClient.REQUIRED_REVISION + ")"; + default: + return null; + } + } + + } + + public static final String CHARSET = "UTF-8"; + + public static final String TIME_ZONE = "UTC"; + + public static final String LOGIN_URL = "/login"; + + public static final String QUERY_URL = "/query?format=tab"; + + public static final String TICKET_URL = "/ticket/"; + + public static final String NEW_TICKET_URL = "/newticket"; + + public static final String TICKET_ATTACHMENT_URL = "/attachment/ticket/"; + + public static final String DEFAULT_USERNAME = "anonymous"; + + /** + * Gets ticket with <code>id</code> from repository. + * + * @param id + * the id of the ticket to get + * @return the ticket + * @throws TracException + * thrown in case of a connection error + */ + TracTicket getTicket(int id) throws TracException; + + /** + * Returns the access type. + */ + Version getVersion(); + + /** + * Queries tickets from repository. All found tickets are added to + * <code>result</code>. + * + * @param query + * the search criteria + * @param result + * the list of found tickets + * @throws TracException + * thrown in case of a connection error + */ + void search(TracSearch query, List<TracTicket> result) throws TracException; + + /** + * Validates the repository connection. + * + * @throws TracException + * thrown in case of a connection error + */ + void validate() throws TracException; + + /** + * Updates cached repository details: milestones, versions etc. + * + * @throws TracException + * thrown in case of a connection error + */ + void updateAttributes(IProgressMonitor monitor, boolean force) throws TracException; + + TracComponent[] getComponents(); + + TracMilestone[] getMilestones(); + + TracPriority[] getPriorities(); + + TracSeverity[] getSeverities(); + + TracTicketResolution[] getTicketResolutions(); + + TracTicketStatus[] getTicketStatus(); + + TracTicketType[] getTicketTypes(); + + TracVersion[] getVersions(); + + byte[] getAttachmentData(int id, String filename) throws TracException; + + void putAttachmentData(int id, String name, String description, byte[] data) throws TracException; + + void createTicket(TracTicket ticket) throws TracException; + + void updateTicket(TracTicket ticket, String comment) throws TracException; + + /** + * Sets a reference to the cached repository attributes. + * + * @param data + * cached repository attributes + */ + void setData(TracClientData data); + + Set<Integer> getChangedTickets(Date since) throws TracException; + + void setProxy(Proxy proxy); + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/InvalidTicketException.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/InvalidTicketException.java new file mode 100644 index 000000000..f5102eaa9 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/InvalidTicketException.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +/** + * Indicates an error while parsing a ticket retrieved from a repository. + * + * @author Steffen Pingel + */ +public class InvalidTicketException extends TracException { + + private static final long serialVersionUID = 7716941243394876876L; + + public InvalidTicketException(String message) { + super(message); + } + + public InvalidTicketException() { + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/Trac09Client.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/Trac09Client.java new file mode 100644 index 000000000..9e114f26a --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/Trac09Client.java @@ -0,0 +1,478 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import javax.security.auth.login.LoginException; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.mylar.context.core.MylarStatusHandler; +import org.eclipse.mylar.internal.tasks.core.HtmlStreamTokenizer; +import org.eclipse.mylar.internal.tasks.core.HtmlTag; +import org.eclipse.mylar.internal.tasks.core.WebClientUtil; +import org.eclipse.mylar.internal.tasks.core.HtmlStreamTokenizer.Token; +import org.eclipse.mylar.internal.trac.core.model.TracComponent; +import org.eclipse.mylar.internal.trac.core.model.TracMilestone; +import org.eclipse.mylar.internal.trac.core.model.TracPriority; +import org.eclipse.mylar.internal.trac.core.model.TracSearch; +import org.eclipse.mylar.internal.trac.core.model.TracSearchFilter; +import org.eclipse.mylar.internal.trac.core.model.TracSeverity; +import org.eclipse.mylar.internal.trac.core.model.TracTicket; +import org.eclipse.mylar.internal.trac.core.model.TracTicketResolution; +import org.eclipse.mylar.internal.trac.core.model.TracTicketStatus; +import org.eclipse.mylar.internal.trac.core.model.TracTicketType; +import org.eclipse.mylar.internal.trac.core.model.TracVersion; +import org.eclipse.mylar.internal.trac.core.model.TracSearchFilter.CompareOperator; +import org.eclipse.mylar.internal.trac.core.model.TracTicket.Key; +import org.eclipse.mylar.internal.trac.core.util.TracHttpClientTransportFactory.TracHttpException; + +/** + * Represents a Trac repository that is accessed through the Trac's query script + * and web interface. + * + * @author Steffen Pingel + */ +public class Trac09Client extends AbstractTracClient { + + private HttpClient httpClient = new HttpClient(); + + private boolean authenticated; + + public Trac09Client(URL url, Version version, String username, String password) { + super(url, version, username, password); + } + + private GetMethod connect(String serverURL) throws TracException { + try { + return connectInternal(serverURL); + } catch (TracException e) { + throw e; + } catch (Exception e) { + throw new TracException(e); + } + } + + private GetMethod connectInternal(String serverURL) throws TracLoginException, IOException, TracHttpException { + WebClientUtil.setupHttpClient(httpClient, proxy, serverURL); + + for (int attempt = 0; attempt < 2; attempt++) { + // force authentication + if (!authenticated && hasAuthenticationCredentials()) { + authenticate(); + } + + GetMethod method = new GetMethod(WebClientUtil.getRequestPath(serverURL)); + method.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); + int code; + try { + code = httpClient.executeMethod(method); + } catch (IOException e) { + method.releaseConnection(); + throw e; + } + + if (code == HttpURLConnection.HTTP_OK) { + return method; + } else if (code == HttpURLConnection.HTTP_UNAUTHORIZED || code == HttpURLConnection.HTTP_FORBIDDEN) { + // login or reauthenticate due to an expired session + method.releaseConnection(); + authenticated = false; + authenticate(); + } else { + throw new TracHttpException(code); + } + } + + throw new TracLoginException(); + } + + private void authenticate() throws TracLoginException, IOException { + if (!hasAuthenticationCredentials()) { + throw new TracLoginException(); + } + + Credentials credentials = new UsernamePasswordCredentials(username, password); + httpClient.getState().setCredentials( + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM), credentials); + + GetMethod method = new GetMethod(WebClientUtil.getRequestPath(repositoryUrl + LOGIN_URL)); + method.setFollowRedirects(false); + + try { + httpClient.getParams().setAuthenticationPreemptive(true); + int code = httpClient.executeMethod(method); + if (code == HttpURLConnection.HTTP_UNAUTHORIZED || code == HttpURLConnection.HTTP_FORBIDDEN) { + throw new TracLoginException(); + } + } finally { + method.releaseConnection(); + httpClient.getParams().setAuthenticationPreemptive(false); + } + + authenticated = true; + } + + /** + * Fetches the web site of a single ticket and returns the Trac ticket. + * + * @param id + * Trac id of ticket + * @throws LoginException + */ + public TracTicket getTicket(int id) throws TracException { + GetMethod method = connect(repositoryUrl + ITracClient.TICKET_URL + id); + try { + TracTicket ticket = new TracTicket(id); + + BufferedReader reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), + ITracClient.CHARSET)); + HtmlStreamTokenizer tokenizer = new HtmlStreamTokenizer(reader, null); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD) { + String headers = tag.getAttribute("headers"); + if ("h_component".equals(headers)) { + ticket.putBuiltinValue(Key.COMPONENT, getText(tokenizer)); + } else if ("h_milestone".equals(headers)) { + ticket.putBuiltinValue(Key.MILESTONE, getText(tokenizer)); + } else if ("h_priority".equals(headers)) { + ticket.putBuiltinValue(Key.PRIORITY, getText(tokenizer)); + } else if ("h_severity".equals(headers)) { + ticket.putBuiltinValue(Key.SEVERITY, getText(tokenizer)); + } else if ("h_version".equals(headers)) { + ticket.putBuiltinValue(Key.VERSION, getText(tokenizer)); + } else if ("h_keywords".equals(headers)) { + ticket.putBuiltinValue(Key.KEYWORDS, getText(tokenizer)); + } else if ("h_cc".equals(headers)) { + ticket.putBuiltinValue(Key.CC, getText(tokenizer)); + } else if ("h_owner".equals(headers)) { + ticket.putBuiltinValue(Key.OWNER, getText(tokenizer)); + } else if ("h_reporter".equals(headers)) { + ticket.putBuiltinValue(Key.REPORTER, getText(tokenizer)); + } + // TODO handle custom fields + } else if (tag.getTagType() == HtmlTag.Type.H2 && "summary".equals(tag.getAttribute("class"))) { + ticket.putBuiltinValue(Key.SUMMARY, getText(tokenizer)); + } else if (tag.getTagType() == HtmlTag.Type.H3 && "status".equals(tag.getAttribute("class"))) { + String text = getStrongText(tokenizer); + if (text.length() > 0) { + int i = text.indexOf(" ("); + if (i != -1) { + // status contains resolution as well + ticket.putBuiltinValue(Key.STATUS, text.substring(0, i)); + ticket.putBuiltinValue(Key.RESOLUTION, text.substring(i, text.length() - 1)); + } else { + ticket.putBuiltinValue(Key.STATUS, text); + } + } + } + // TODO parse description + } + } + + if (ticket.isValid() && ticket.getValue(Key.SUMMARY) != null) { + return ticket; + } + + throw new InvalidTicketException(); + } catch (IOException e) { + throw new TracException(e); + } catch (ParseException e) { + throw new TracException(e); + } finally { + method.releaseConnection(); + } + } + + public void search(TracSearch query, List<TracTicket> tickets) throws TracException { + GetMethod method = connect(repositoryUrl + ITracClient.QUERY_URL + query.toUrl()); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), + ITracClient.CHARSET)); + String line; + + Map<String, String> constantValues = getExactMatchValues(query); + + // first line contains names of returned ticket fields + line = reader.readLine(); + if (line == null) { + throw new InvalidTicketException(); + } + StringTokenizer t = new StringTokenizer(line, "\t"); + Key[] fields = new Key[t.countTokens()]; + for (int i = 0; i < fields.length; i++) { + fields[i] = Key.fromKey(t.nextToken()); + } + + // create a ticket for each following line of output + while ((line = reader.readLine()) != null) { + t = new StringTokenizer(line, "\t"); + TracTicket ticket = new TracTicket(); + for (int i = 0; i < fields.length && t.hasMoreTokens(); i++) { + if (fields[i] != null) { + try { + if (fields[i] == Key.ID) { + ticket.setId(Integer.parseInt(t.nextToken())); + } else if (fields[i] == Key.TIME) { + ticket.setCreated(Integer.parseInt(t.nextToken())); + } else if (fields[i] == Key.CHANGE_TIME) { + ticket.setLastChanged(Integer.parseInt(t.nextToken())); + } else { + ticket.putBuiltinValue(fields[i], parseTicketValue(t.nextToken())); + } + } catch (NumberFormatException e) { + MylarStatusHandler.log(e, "Error parsing repsonse: " + line); + } + } + } + + if (ticket.isValid()) { + for (String key : constantValues.keySet()) { + ticket.putValue(key, parseTicketValue(constantValues.get(key))); + } + + tickets.add(ticket); + } + } + } catch (IOException e) { + throw new TracException(e); + } finally { + method.releaseConnection(); + } + } + + /** + * Trac has sepcial encoding rules for the returned output: None is + * represented by "--". + */ + private String parseTicketValue(String value) { + if ("--".equals(value)) { + return ""; + } + return value; + } + + /** + * Extracts constant values from <code>query</code>. The Trac query + * script does not return fields that matched exactly againt a single value. + */ + private Map<String, String> getExactMatchValues(TracSearch query) { + Map<String, String> values = new HashMap<String, String>(); + List<TracSearchFilter> filters = query.getFilters(); + for (TracSearchFilter filter : filters) { + if (filter.getOperator() == CompareOperator.IS && filter.getValues().size() == 1) { + values.put(filter.getFieldName(), filter.getValues().get(0)); + } + } + return values; + } + + public void validate() throws TracException { + GetMethod method = connect(repositoryUrl + "/"); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), + ITracClient.CHARSET)); + HtmlStreamTokenizer tokenizer = new HtmlStreamTokenizer(reader, null); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.A) { + String id = tag.getAttribute("id"); + if ("tracpowered".equals(id)) { + return; + } + } + } + } + + throw new TracException("Not a valid Trac repository"); + } catch (IOException e) { + throw new TracException(e); + } catch (ParseException e) { + throw new TracException(e); + } finally { + method.releaseConnection(); + } + } + + public void updateAttributes(IProgressMonitor monitor) throws TracException { + monitor.beginTask("Updating attributes", IProgressMonitor.UNKNOWN); + + GetMethod method = connect(repositoryUrl + ITracClient.NEW_TICKET_URL); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), + ITracClient.CHARSET)); + HtmlStreamTokenizer tokenizer = new HtmlStreamTokenizer(reader, null); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT) { + String name = tag.getAttribute("id"); + if ("component".equals(name)) { + List<String> values = getOptionValues(tokenizer); + data.components = new ArrayList<TracComponent>(values.size()); + for (String value : values) { + data.components.add(new TracComponent(value)); + } + } else if ("milestone".equals(name)) { + List<String> values = getOptionValues(tokenizer); + data.milestones = new ArrayList<TracMilestone>(values.size()); + for (String value : values) { + data.milestones.add(new TracMilestone(value)); + } + } else if ("priority".equals(name)) { + List<String> values = getOptionValues(tokenizer); + data.priorities = new ArrayList<TracPriority>(values.size()); + for (int i = 0; i < values.size(); i++) { + data.priorities.add(new TracPriority(values.get(i), i + 1)); + } + } else if ("severity".equals(name)) { + List<String> values = getOptionValues(tokenizer); + data.severities = new ArrayList<TracSeverity>(values.size()); + for (int i = 0; i < values.size(); i++) { + data.severities.add(new TracSeverity(values.get(i), i + 1)); + } + } else if ("type".equals(name)) { + List<String> values = getOptionValues(tokenizer); + data.ticketTypes = new ArrayList<TracTicketType>(values.size()); + for (int i = 0; i < values.size(); i++) { + data.ticketTypes.add(new TracTicketType(values.get(i), i + 1)); + } + } else if ("version".equals(name)) { + List<String> values = getOptionValues(tokenizer); + data.versions = new ArrayList<TracVersion>(values.size()); + for (String value : values) { + data.versions.add(new TracVersion(value)); + } + } + } + } + } + + data.ticketResolutions = new ArrayList<TracTicketResolution>(5); + data.ticketResolutions.add(new TracTicketResolution("fixed", 1)); + data.ticketResolutions.add(new TracTicketResolution("invalid", 2)); + data.ticketResolutions.add(new TracTicketResolution("wontfix", 3)); + data.ticketResolutions.add(new TracTicketResolution("duplicate", 4)); + data.ticketResolutions.add(new TracTicketResolution("worksforme", 5)); + + data.ticketStatus = new ArrayList<TracTicketStatus>(4); + data.ticketStatus.add(new TracTicketStatus("new", 1)); + data.ticketStatus.add(new TracTicketStatus("assigned", 2)); + data.ticketStatus.add(new TracTicketStatus("reopened", 3)); + data.ticketStatus.add(new TracTicketStatus("closed", 4)); + } catch (IOException e) { + throw new TracException(e); + } catch (ParseException e) { + throw new TracException(e); + } finally { + method.releaseConnection(); + } + } + + private List<String> getOptionValues(HtmlStreamTokenizer tokenizer) throws IOException, ParseException { + List<String> values = new ArrayList<String>(); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.OPTION && !tag.isEndTag()) { + String value = getText(tokenizer).trim(); + if (value.length() > 0) { + values.add(value); + } + } else { + return values; + } + } + } + return values; + } + + private String getText(HtmlStreamTokenizer tokenizer) throws IOException, ParseException { + StringBuffer sb = new StringBuffer(); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TEXT) { + sb.append(token.toString()); + } else if (token.getType() == Token.COMMENT) { + // ignore + } else { + break; + } + } + return HtmlStreamTokenizer.unescape(sb).toString(); + } + + /** + * Looks for a <code>strong</code> tag and returns the text enclosed by + * the tag. + */ + private String getStrongText(HtmlStreamTokenizer tokenizer) throws IOException, ParseException { + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG && ((HtmlTag) token.getValue()).getTagType() == HtmlTag.Type.STRONG) { + return getText(tokenizer); + } else if (token.getType() == Token.COMMENT) { + // ignore + } else if (token.getType() == Token.TEXT) { + // ignore + } else { + break; + } + } + return ""; + } + + public byte[] getAttachmentData(int id, String filename) throws TracException { + throw new TracException("Unsupported operation"); + } + + public void putAttachmentData(int id, String name, String description, byte[] data) throws TracException { + throw new TracException("Unsupported operation"); + } + + public void createTicket(TracTicket ticket) throws TracException { + throw new TracException("Unsupported operation"); + } + + public void updateTicket(TracTicket ticket, String comment) throws TracException { + throw new TracException("Unsupported operation"); + } + + public Set<Integer> getChangedTickets(Date since) throws TracException { + return null; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeFactory.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeFactory.java new file mode 100644 index 000000000..4c254dfba --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeFactory.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mylar.internal.trac.core.model.TracTicket.Key; +import org.eclipse.mylar.tasks.core.AbstractAttributeFactory; +import org.eclipse.mylar.tasks.core.RepositoryTaskAttribute; + +/** + * Provides a mapping from Mylar task keys to Trac ticket keys. + * + * @author Steffen Pingel + */ +public class TracAttributeFactory extends AbstractAttributeFactory { + + private static final long serialVersionUID = 5333211422546115138L; + + private static Map<String, Attribute> attributeByTracKey = new HashMap<String, Attribute>(); + + private static Map<String, String> tracKeyByTaskKey = new HashMap<String, String>(); + + public enum Attribute { + CC(Key.CC, "CC:", RepositoryTaskAttribute.USER_CC), + CHANGE_TIME(Key.CHANGE_TIME, "Last Modification:", RepositoryTaskAttribute.DATE_MODIFIED, true, true), + COMPONENT(Key.COMPONENT, "Component:", null), + DESCRIPTION(Key.DESCRIPTION, "Description:", RepositoryTaskAttribute.DESCRIPTION, true, false), + ID(Key.ID, "<used by search engine>", null, true), + KEYWORDS(Key.KEYWORDS, "Keywords:", RepositoryTaskAttribute.KEYWORDS), + MILESTONE(Key.MILESTONE, "Milestone:", null), + OWNER(Key.OWNER, "Owner:", RepositoryTaskAttribute.USER_OWNER, false, true), + PRIORITY(Key.PRIORITY, "Priority:", null), + REPORTER(Key.REPORTER, "Reporter:", RepositoryTaskAttribute.USER_REPORTER, false, true), + RESOLUTION(Key.RESOLUTION, "Resolution:", RepositoryTaskAttribute.RESOLUTION, false, true), + SEVERITY(Key.SEVERITY, "Severity:", null), + STATUS(Key.STATUS, "Status:", RepositoryTaskAttribute.STATUS, false, true), + SUMMARY(Key.SUMMARY, "Summary:", RepositoryTaskAttribute.SUMMARY, true), + TIME(Key.TIME, "Created:", RepositoryTaskAttribute.DATE_CREATION, true, true), + TYPE(Key.TYPE, "Type:", null), + VERSION(Key.VERSION, "Version:", null); + + private final boolean isHidden; + + private final boolean isReadOnly; + + private final String tracKey; + + private final String prettyName; + + private final String taskKey; + + Attribute(Key key, String prettyName, String taskKey, boolean hidden, boolean readonly) { + this.tracKey = key.getKey(); + this.taskKey = taskKey; + this.prettyName = prettyName; + this.isHidden = hidden; + this.isReadOnly = readonly; + + attributeByTracKey.put(tracKey, this); + if (taskKey != null) { + tracKeyByTaskKey.put(taskKey, tracKey); + } + } + + Attribute(Key key, String prettyName, String taskKey, boolean hidden) { + this(key, prettyName, taskKey, hidden, false); + } + + Attribute(Key key, String prettyName, String taskKey) { + this(key, prettyName, taskKey, false, false); + } + + public String getTaskKey() { + return taskKey; + } + + public String getTracKey() { + return tracKey; + } + + public boolean isHidden() { + return isHidden; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + public String toString() { + return prettyName; + } + } + + + @Override + public boolean getIsHidden(String key) { + Attribute attribute = attributeByTracKey.get(key); + return (attribute != null) ? attribute.isHidden() : false; + } + + @Override + public String getName(String key) { + Attribute attribute = attributeByTracKey.get(key); + // TODO if attribute == null it is probably a custom field: need + // to query custom field information from repoository + return (attribute != null) ? attribute.toString() : key; + } + + @Override + public boolean isReadOnly(String key) { + Attribute attribute = attributeByTracKey.get(key); + return (attribute != null) ? attribute.isReadOnly() : false; + } + + @Override + public String mapCommonAttributeKey(String key) { + String tracKey = tracKeyByTaskKey.get(key); + return (tracKey != null) ? tracKey : key; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientData.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientData.java new file mode 100644 index 000000000..41fded092 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientData.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.io.Serializable; +import java.util.List; + +import org.eclipse.mylar.internal.trac.core.model.TracComponent; +import org.eclipse.mylar.internal.trac.core.model.TracMilestone; +import org.eclipse.mylar.internal.trac.core.model.TracPriority; +import org.eclipse.mylar.internal.trac.core.model.TracSeverity; +import org.eclipse.mylar.internal.trac.core.model.TracTicketResolution; +import org.eclipse.mylar.internal.trac.core.model.TracTicketStatus; +import org.eclipse.mylar.internal.trac.core.model.TracTicketType; +import org.eclipse.mylar.internal.trac.core.model.TracVersion; + +public class TracClientData implements Serializable { + + private static final long serialVersionUID = 6891961984245981675L; + + List<TracComponent> components; + + List<TracMilestone> milestones; + + List<TracPriority> priorities; + + List<TracSeverity> severities; + + List<TracTicketResolution> ticketResolutions; + + List<TracTicketStatus> ticketStatus; + + List<TracTicketType> ticketTypes; + + List<TracVersion> versions; + + long lastUpdate; + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientFactory.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientFactory.java new file mode 100644 index 000000000..50b7ea6e3 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientFactory.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.mylar.internal.trac.core.ITracClient.Version; + +/** + * @author Steffen Pingel + */ +public class TracClientFactory { + + public static ITracClient createClient(String location, Version version, String username, String password) + throws MalformedURLException { + URL url = new URL(location); + + if (version == Version.TRAC_0_9) { + return new Trac09Client(url, version, username, password); + } else if (version == Version.XML_RPC) { + return new TracXmlRpcClient(url, version, username, password); + } + + throw new RuntimeException("Invalid repository version: " + version); + } + + /** + * Tries all supported access types for <code>location</code> and returns + * the corresponding version if successful; throws an exception otherwise. + * + * <p> + * Order of the tried access types: XML-RPC, Trac 0.9 + */ + public static Version probeClient(String location, String username, String password) throws MalformedURLException, + TracException { + URL url = new URL(location); + try { + ITracClient repository = new TracXmlRpcClient(url, Version.XML_RPC, username, password); + repository.validate(); + return Version.XML_RPC; + } catch (TracException e) { + try { + ITracClient repository = new Trac09Client(url, Version.TRAC_0_9, username, password); + repository.validate(); + return Version.TRAC_0_9; + } catch (TracLoginException e2) { + throw e; + } catch (TracException e2) { + } + } + + throw new TracException(); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientManager.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientManager.java new file mode 100644 index 000000000..329b7104f --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracClientManager.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mylar.internal.trac.core.ITracClient.Version; +import org.eclipse.mylar.tasks.core.ITaskRepositoryListener; +import org.eclipse.mylar.tasks.core.TaskRepository; + +/** + * Caches {@link ITracClient} objects. + * + * @author Steffen Pingel + */ +public class TracClientManager implements ITaskRepositoryListener { + + private Map<String, ITracClient> clientByUrl = new HashMap<String, ITracClient>(); + + private Map<String, TracClientData> clientDataByUrl = new HashMap<String, TracClientData>(); + + private File cacheFile; + + public TracClientManager(File cacheFile) { + this.cacheFile = cacheFile; + + readCache(); + } + + public synchronized ITracClient getRepository(TaskRepository taskRepository) throws MalformedURLException { + ITracClient repository = clientByUrl.get(taskRepository.getUrl()); + if (repository == null) { + repository = TracClientFactory.createClient(taskRepository.getUrl(), Version.fromVersion(taskRepository + .getVersion()), taskRepository.getUserName(), taskRepository.getPassword()); + clientByUrl.put(taskRepository.getUrl(), repository); + + TracClientData data = clientDataByUrl.get(taskRepository.getUrl()); + if (data == null) { + data = new TracClientData(); + clientDataByUrl.put(taskRepository.getUrl(), data); + } + repository.setData(data); + } + return repository; + } + + public void repositoriesRead() { + // ignore + } + + public synchronized void repositoryAdded(TaskRepository repository) { + // make sure there is no stale client still in the cache, bug #149939 + clientByUrl.remove(repository.getUrl()); + clientDataByUrl.remove(repository.getUrl()); + } + + public synchronized void repositoryRemoved(TaskRepository repository) { + clientByUrl.remove(repository.getUrl()); + clientDataByUrl.remove(repository.getUrl()); + } + + public synchronized void repositorySettingsChanged(TaskRepository repository) { + clientByUrl.remove(repository.getUrl()); + // if url is changed a stale data object will be left in clientDataByUrl, bug #149939 + } + + @SuppressWarnings("unchecked") + public void readCache() { + if (cacheFile == null || !cacheFile.exists()) { + return; + } + + ObjectInputStream in = null; + try { + in = new ObjectInputStream(new FileInputStream(cacheFile)); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String url = (String) in.readObject(); + TracClientData data = (TracClientData) in.readObject(); + if (url != null && data != null) { + clientDataByUrl.put(url, data); + } + } + } catch (Throwable e) { + TracCorePlugin.log(e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + + } + + public void writeCache() { + if (cacheFile == null) { + return; + } + + ObjectOutputStream out = null; + try { + out = new ObjectOutputStream(new FileOutputStream(cacheFile)); + out.writeInt(clientDataByUrl.size()); + for (String url : clientDataByUrl.keySet()) { + out.writeObject(url); + out.writeObject(clientDataByUrl.get(url)); + } + } catch (IOException e) { + TracCorePlugin.log(e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } + } + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java new file mode 100644 index 000000000..a9a2389df --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2006 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.internal.trac.core; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.BundleContext; + +/** + * The headless Trac plug-in class. + * + * @author Steffen Pingel + */ +public class TracCorePlugin extends Plugin { + + public static final String PLUGIN_ID = "org.eclipse.mylar.trac.core"; + + public static final String ENCODING_UTF_8 = "UTF-8"; + + private static TracCorePlugin plugin; + + public final static String REPOSITORY_KIND = "trac"; + + public TracCorePlugin() { + } + + public static TracCorePlugin getDefault() { + return plugin; + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Convenience method for logging statuses to the plug-in log + * + * @param status + * the status to log + */ + public static void log(IStatus status) { + getDefault().getLog().log(status); + } + + /** + * Convenience method for logging exceptions to the plug-in log + * + * @param e + * the exception to log + */ + public static void log(Throwable e) { + String message = e.getMessage(); + if (e.getMessage() == null) { + message = e.getClass().toString(); + } + log(new Status(Status.ERROR, TracCorePlugin.PLUGIN_ID, 0, message, e)); + } + +} + + diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracException.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracException.java new file mode 100644 index 000000000..f38b02a4a --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracException.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +/** + * Indicates an error during repository access. + * + * @author Steffen Pingel + */ +public class TracException extends Exception { + + private static final long serialVersionUID = 1929614326467463462L; + + public TracException() { + } + + public TracException(String message) { + super(message); + } + + public TracException(Throwable cause) { + super(cause); + } + + public TracException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracLoginException.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracLoginException.java new file mode 100644 index 000000000..d50aff94b --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracLoginException.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +/** + * Indicates an authentication error during login. + * + * @author Steffen Pingel + */ +public class TracLoginException extends TracException { + + private static final long serialVersionUID = -6128773690643367414L; + + public TracLoginException() { + } + + public TracLoginException(String message) { + super(message); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRemoteException.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRemoteException.java new file mode 100644 index 000000000..3923de9b8 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRemoteException.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core; + +/** + * Indicates that an exception on the repository side has been encountered while + * processing the request. + * + * @author Steffen Pingel + */ +public class TracRemoteException extends TracException { + + private static final long serialVersionUID = -6761365344287289624L; + + public TracRemoteException() { + } + + public TracRemoteException(String message) { + super(message); + } + + public TracRemoteException(Throwable cause) { + super(cause); + } + + public TracRemoteException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracXmlRpcClient.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracXmlRpcClient.java new file mode 100644 index 000000000..8e7a694fa --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracXmlRpcClient.java @@ -0,0 +1,449 @@ +package org.eclipse.mylar.internal.trac.core; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import org.apache.xmlrpc.XmlRpcException; +import org.apache.xmlrpc.client.XmlRpcClient; +import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.mylar.context.core.MylarStatusHandler; +import org.eclipse.mylar.internal.trac.core.model.TracAttachment; +import org.eclipse.mylar.internal.trac.core.model.TracComment; +import org.eclipse.mylar.internal.trac.core.model.TracComponent; +import org.eclipse.mylar.internal.trac.core.model.TracMilestone; +import org.eclipse.mylar.internal.trac.core.model.TracPriority; +import org.eclipse.mylar.internal.trac.core.model.TracSearch; +import org.eclipse.mylar.internal.trac.core.model.TracSeverity; +import org.eclipse.mylar.internal.trac.core.model.TracTicket; +import org.eclipse.mylar.internal.trac.core.model.TracTicketResolution; +import org.eclipse.mylar.internal.trac.core.model.TracTicketStatus; +import org.eclipse.mylar.internal.trac.core.model.TracTicketType; +import org.eclipse.mylar.internal.trac.core.model.TracVersion; +import org.eclipse.mylar.internal.trac.core.model.TracTicket.Key; +import org.eclipse.mylar.internal.trac.core.util.TracHttpClientTransportFactory; +import org.eclipse.mylar.internal.trac.core.util.TracUtils; +import org.eclipse.mylar.internal.trac.core.util.TracHttpClientTransportFactory.TracHttpException; + +/** + * Represents a Trac repository that is accessed through the Trac XmlRpcPlugin. + * + * @author Steffen Pingel + */ +public class TracXmlRpcClient extends AbstractTracClient { + + public static final String XMLRPC_URL = "/xmlrpc"; + + public static final String REQUIRED_REVISION = "1188"; + + private XmlRpcClient xmlrpc; + + public TracXmlRpcClient(URL url, Version version, String username, String password) { + super(url, version, username, password); + } + + public synchronized XmlRpcClient getClient() throws TracException { + if (xmlrpc != null) { + return xmlrpc; + } + + XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); + config.setEncoding(ITracClient.CHARSET); + config.setBasicUserName(username); + config.setBasicPassword(password); + config.setServerURL(getXmlRpcUrl()); + config.setTimeZone(TimeZone.getTimeZone(ITracClient.TIME_ZONE)); + + xmlrpc = new XmlRpcClient(); + xmlrpc.setConfig(config); + + TracHttpClientTransportFactory factory = new TracHttpClientTransportFactory(xmlrpc); + xmlrpc.setTransportFactory(factory); + + return xmlrpc; + } + + private URL getXmlRpcUrl() throws TracException { + try { + String location = repositoryUrl.toString(); + if (hasAuthenticationCredentials()) { + location += LOGIN_URL; + } + location += XMLRPC_URL; + + return new URL(location); + } catch (Exception e) { + throw new TracException(e); + } + } + + private Object call(String method, Object... parameters) throws TracException { + getClient(); + + try { + return xmlrpc.execute(method, parameters); + } catch (TracHttpException e) { + if (e.code == HttpURLConnection.HTTP_FORBIDDEN || e.code == HttpURLConnection.HTTP_UNAUTHORIZED) { + throw new TracLoginException(); + } else { + throw new TracException(e); + } + } catch (XmlRpcException e) { + throw new TracRemoteException(e); + } catch (Exception e) { + throw new TracException(e); + } + } + + private Object[] multicall(Map<String, Object>... calls) throws TracException { + Object[] result = (Object[]) call("system.multicall", new Object[] { calls }); + for (Object item : result) { + try { + checkForException(item); + } catch (XmlRpcException e) { + throw new TracRemoteException(e); + } catch (Exception e) { + throw new TracException(e); + } + } + return result; + } + + private void checkForException(Object result) throws NumberFormatException, XmlRpcException { + if (result instanceof Map) { + Map exceptionData = (Map) result; + if (exceptionData.containsKey("faultCode") && exceptionData.containsKey("faultString")) { + throw new XmlRpcException(Integer.parseInt(exceptionData.get("faultCode").toString()), + (String) exceptionData.get("faultString")); + } + } + } + + private Map<String, Object> createMultiCall(String methodName, Object... parameters) throws TracException { + Map<String, Object> table = new HashMap<String, Object>(); + table.put("methodName", methodName); + table.put("params", parameters); + return table; + } + + private Object getMultiCallResult(Object item) { + return ((Object[]) item)[0]; + } + + public void validate() throws TracException { + Object[] result = (Object[]) call("system.listMethods"); + boolean hasGetTicket = false, hasQuery = false, isRecentRevision = false; + for (Object methodName : result) { + if ("ticket.get".equals(methodName)) { + hasGetTicket = true; + } + if ("ticket.query".equals(methodName)) { + hasQuery = true; + } + if ("ticket.getRecentChanges".equals(methodName)) { + // this call was added in rev. 1188 + isRecentRevision = true; + } + + if (hasGetTicket && hasQuery && isRecentRevision) { + return; + } + } + + throw new TracException("Required API calls are missing, please update your Trac XML-RPC Plugin to revision " + REQUIRED_REVISION + " or later"); + } + + public TracTicket getTicket(int id) throws TracException { + Object[] result = (Object[]) call("ticket.get", id); + TracTicket ticket = parseTicket(result); + + result = (Object[]) call("ticket.changeLog", id, 0); + for (Object item : result) { + ticket.addComment(parseChangeLogEntry((Object[]) item)); + } + + result = (Object[]) call("ticket.listAttachments", id); + for (Object item : result) { + ticket.addAttachment(parseAttachment((Object[]) item)); + } + + String[] actions = getActions(id); + ticket.setActions(actions); + + ticket.setResolutions(getDefaultTicketResolutions()); + + return ticket; + } + + private TracAttachment parseAttachment(Object[] entry) { + TracAttachment attachment = new TracAttachment((String) entry[0]); + attachment.setDescription((String) entry[1]); + attachment.setSize((Integer) entry[2]); + attachment.setCreated(TracUtils.parseDate((Integer) entry[3])); + attachment.setAuthor((String) entry[4]); + return attachment; + } + + private TracComment parseChangeLogEntry(Object[] entry) { + TracComment comment = new TracComment(); + comment.setCreated(TracUtils.parseDate((Integer) entry[0])); + comment.setAuthor((String) entry[1]); + comment.setField((String) entry[2]); + comment.setOldValue((String) entry[3]); + comment.setNewValue((String) entry[4]); + return comment; + } + + /* public for testing */ + @SuppressWarnings("unchecked") + public List<TracTicket> getTickets(int[] ids) throws TracException { + Map<String, Object>[] calls = new Map[ids.length]; + for (int i = 0; i < calls.length; i++) { + calls[i] = createMultiCall("ticket.get", ids[i]); + } + + Object[] result = multicall(calls); + assert result.length == ids.length; + + List<TracTicket> tickets = new ArrayList<TracTicket>(result.length); + for (Object item : result) { + Object[] ticketResult = (Object[]) getMultiCallResult(item); + tickets.add(parseTicket(ticketResult)); + } + + return tickets; + } + + @SuppressWarnings("unchecked") + public void search(TracSearch query, List<TracTicket> tickets) throws TracException { + // an empty query string is not valid, therefore prepend order + Object[] result = (Object[]) call("ticket.query", "order=id" + query.toQuery()); + + Map<String, Object>[] calls = new Map[result.length]; + for (int i = 0; i < calls.length; i++) { + calls[i] = createMultiCall("ticket.get", result[i]); + } + result = multicall(calls); + + for (Object item : result) { + Object[] ticketResult = (Object[]) getMultiCallResult(item); + tickets.add(parseTicket(ticketResult)); + } + } + + private TracTicket parseTicket(Object[] ticketResult) throws InvalidTicketException { + TracTicket ticket = new TracTicket((Integer) ticketResult[0]); + ticket.setCreated((Integer) ticketResult[1]); + ticket.setLastChanged((Integer) ticketResult[2]); + Map attributes = (Map) ticketResult[3]; + for (Object key : attributes.keySet()) { + ticket.putValue(key.toString(), attributes.get(key).toString()); + } + return ticket; + } + + public synchronized void updateAttributes(IProgressMonitor monitor) throws TracException { + monitor.beginTask("Updating attributes", 8); + + Object[] result = getAttributes("ticket.component"); + data.components = new ArrayList<TracComponent>(result.length); + for (Object item : result) { + data.components.add(parseComponent((Map) getMultiCallResult(item))); + } + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + result = getAttributes("ticket.milestone"); + data.milestones = new ArrayList<TracMilestone>(result.length); + for (Object item : result) { + data.milestones.add(parseMilestone((Map) getMultiCallResult(item))); + } + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + List<TicketAttributeResult> attributes = getTicketAttributes("ticket.priority"); + data.priorities = new ArrayList<TracPriority>(result.length); + for (TicketAttributeResult attribute : attributes) { + data.priorities.add(new TracPriority(attribute.name, attribute.value)); + } + Collections.sort(data.priorities); + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + attributes = getTicketAttributes("ticket.resolution"); + data.ticketResolutions = new ArrayList<TracTicketResolution>(result.length); + for (TicketAttributeResult attribute : attributes) { + data.ticketResolutions.add(new TracTicketResolution(attribute.name, attribute.value)); + } + Collections.sort(data.ticketResolutions); + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + attributes = getTicketAttributes("ticket.severity"); + data.severities = new ArrayList<TracSeverity>(result.length); + for (TicketAttributeResult attribute : attributes) { + data.severities.add(new TracSeverity(attribute.name, attribute.value)); + } + Collections.sort(data.severities); + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + attributes = getTicketAttributes("ticket.status"); + data.ticketStatus = new ArrayList<TracTicketStatus>(result.length); + for (TicketAttributeResult attribute : attributes) { + data.ticketStatus.add(new TracTicketStatus(attribute.name, attribute.value)); + } + Collections.sort(data.ticketStatus); + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + attributes = getTicketAttributes("ticket.type"); + data.ticketTypes = new ArrayList<TracTicketType>(result.length); + for (TicketAttributeResult attribute : attributes) { + data.ticketTypes.add(new TracTicketType(attribute.name, attribute.value)); + } + Collections.sort(data.ticketTypes); + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + result = getAttributes("ticket.version"); + data.versions = new ArrayList<TracVersion>(result.length); + for (Object item : result) { + data.versions.add(parseVersion((Map) getMultiCallResult(item))); + } + monitor.worked(1); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + } + + private TracComponent parseComponent(Map result) { + TracComponent component = new TracComponent((String) result.get("name")); + component.setOwner((String) result.get("owner")); + component.setDescription((String) result.get("description")); + return component; + } + + private TracMilestone parseMilestone(Map result) { + TracMilestone milestone = new TracMilestone((String) result.get("name")); + milestone.setCompleted(TracUtils.parseDate((Integer) result.get("completed"))); + milestone.setDue(TracUtils.parseDate((Integer) result.get("due"))); + milestone.setDescription((String) result.get("description")); + return milestone; + } + + private TracVersion parseVersion(Map result) { + TracVersion version = new TracVersion((String) result.get("name")); + version.setTime(TracUtils.parseDate((Integer) result.get("time"))); + version.setDescription((String) result.get("description")); + return version; + } + + @SuppressWarnings("unchecked") + private Object[] getAttributes(String attributeType) throws TracException { + Object[] ids = (Object[]) call(attributeType + ".getAll"); + Map<String, Object>[] calls = new Map[ids.length]; + for (int i = 0; i < calls.length; i++) { + calls[i] = createMultiCall(attributeType + ".get", ids[i]); + } + + Object[] result = multicall(calls); + assert result.length == ids.length; + + return result; + } + + @SuppressWarnings("unchecked") + private List<TicketAttributeResult> getTicketAttributes(String attributeType) throws TracException { + Object[] ids = (Object[]) call(attributeType + ".getAll"); + Map<String, Object>[] calls = new Map[ids.length]; + for (int i = 0; i < calls.length; i++) { + calls[i] = createMultiCall(attributeType + ".get", ids[i]); + } + + Object[] result = multicall(calls); + assert result.length == ids.length; + + List<TicketAttributeResult> attributes = new ArrayList<TicketAttributeResult>(result.length); + for (int i = 0; i < calls.length; i++) { + try { + TicketAttributeResult attribute = new TicketAttributeResult(); + attribute.name = (String) ids[i]; + attribute.value = Integer.parseInt((String) getMultiCallResult(result[i])); + attributes.add(attribute); + } catch (NumberFormatException e) { + MylarStatusHandler.log(e, "Invalid response from Trac repository for attribute type: '" + attributeType + + "'"); + } + } + + return attributes; + } + + public byte[] getAttachmentData(int ticketId, String filename) throws TracException { + return (byte[]) call("ticket.getAttachment", ticketId, filename); + } + + public void putAttachmentData(int ticketId, String filename, String description, byte[] data) throws TracException { + call("ticket.putAttachment", ticketId, filename, description, data, true); + } + + private class TicketAttributeResult { + + String name; + + int value; + + } + + public void createTicket(TracTicket ticket) throws TracException { + Map<String, String> attributes = ticket.getValues(); + String summary = attributes.remove(Key.SUMMARY.getKey()); + String description = attributes.remove(Key.DESCRIPTION.getKey()); + if (summary == null || description == null) { + throw new InvalidTicketException(); + } + call("ticket.create", summary, description, attributes); + } + + public void updateTicket(TracTicket ticket, String comment) throws TracException { + Map<String, String> attributes = ticket.getValues(); + call("ticket.update", ticket.getId(), comment, attributes); + } + + public Set<Integer> getChangedTickets(Date since) throws TracException { + Object[] ids; + ids = (Object[]) call("ticket.getRecentChanges", since); + Set<Integer> result = new HashSet<Integer>(); + for (Object id : ids) { + result.add((Integer) id); + } + return result; + } + + public String[] getActions(int id) throws TracException { + Object[] actions = (Object[]) call("ticket.getAvailableActions", id); + String[] result = new String[actions.length]; + for (int i = 0; i < result.length; i++) { + result[i] = (String) actions[i]; + } + return result; + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttachment.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttachment.java new file mode 100644 index 000000000..6125ababb --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttachment.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.util.Date; + +/** + * @author Steffen Pingel + */ +public class TracAttachment { + + private String author; + + private Date created; + + private String description; + + private String filename; + + int size; + + public TracAttachment(String filename) { + this.filename = filename; + } + + public String getAuthor() { + return author; + } + + public Date getCreated() { + return created; + } + + public String getDescription() { + return description; + } + + public String getFilename() { + return filename; + } + + public int getSize() { + return size; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public void setSize(int size) { + this.size = size; + } + + @Override + public String toString() { + return filename; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttribute.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttribute.java new file mode 100644 index 000000000..5962eafba --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttribute.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.io.Serializable; + +/** + * @author Steffen Pingel + */ +public class TracAttribute implements Serializable { + + private static final long serialVersionUID = -4535033208999685315L; + + private String name; + + public TracAttribute(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComment.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComment.java new file mode 100644 index 000000000..d1be7585f --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComment.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.util.Date; + +/** + * @author Steffen Pingel + */ +public class TracComment { + + private String author; + + private Date created; + + private String field; + + private String newValue; + + private String oldValue; + + private boolean permanent; + + public TracComment() { + } + + public String getAuthor() { + return author; + } + + public Date getCreated() { + return created; + } + + public String getField() { + return field; + } + + public String getNewValue() { + return newValue; + } + + public String getOldValue() { + return oldValue; + } + + public boolean isPermanent() { + return permanent; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setField(String field) { + this.field = field; + } + + public void setNewValue(String newValue) { + this.newValue = newValue; + } + + public void setOldValue(String oldValue) { + this.oldValue = oldValue; + } + + public void setPermanent(boolean permanent) { + this.permanent = permanent; + } + + @Override + public String toString() { + return "[" + field + "] " + author + ": " + oldValue + " -> " + newValue; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java new file mode 100644 index 000000000..00088e9e2 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +/** + * @author Steffen Pingel + */ +public class TracComponent extends TracAttribute { + + private static final long serialVersionUID = -6181067219323677076L; + + private String owner; + + private String description; + + public TracComponent(String name) { + super(name); + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java new file mode 100644 index 000000000..935689e12 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author Steffen Pingel + */ +public class TracMilestone extends TracAttribute implements Serializable { + + private static final long serialVersionUID = 6648558552508886484L; + + private Date due; + + private Date completed; + + private String description; + + public TracMilestone(String name) { + super(name); + } + + public Date getCompleted() { + return completed; + } + + public void setCompleted(Date completed) { + this.completed = completed; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getDue() { + return due; + } + + public void setDue(Date due) { + this.due = due; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/trac/core/internal/DELETE.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracPriority.java index edd87f71c..eed975522 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/trac/core/internal/DELETE.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracPriority.java @@ -6,8 +6,17 @@ * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ -package org.eclipse.mylar.trac.core.internal; +package org.eclipse.mylar.internal.trac.core.model; -public class DELETE { +/** + * @author Steffen Pingel + */ +public class TracPriority extends TracTicketAttribute { + private static final long serialVersionUID = 3617078252773178266L; + + public TracPriority(String name, int value) { + super(name, value); + } + } diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSearch.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSearch.java new file mode 100644 index 000000000..bcc8784c8 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSearch.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.mylar.context.core.MylarStatusHandler; +import org.eclipse.mylar.internal.trac.core.ITracClient; +import org.eclipse.mylar.internal.trac.core.model.TracSearchFilter.CompareOperator; + +/** + * Represents a Trac search. A search can have multiple {@link TracSearchFilter}s + * that all need to match. + * + * @author Steffen Pingel + */ +public class TracSearch { + + /** Stores search criteria in the order entered by the user. */ + private Map<String, TracSearchFilter> filterByFieldName = new LinkedHashMap<String, TracSearchFilter>(); + + /** The field the result is ordered by. */ + private String orderBy; + + private boolean ascending = true; + + public TracSearch() { + } + + public void addFilter(String key, String value) { + TracSearchFilter filter = filterByFieldName.get(key); + if (filter == null) { + filter = new TracSearchFilter(key); + CompareOperator operator = CompareOperator.fromUrl(value); + filter.setOperator(operator); + filterByFieldName.put(key, filter); + } + + filter.addValue(value.substring(filter.getOperator().getQueryValue().length())); + } + + public void addFilter(TracSearchFilter filter) { + filterByFieldName.put(filter.getFieldName(), filter); + } + + public List<TracSearchFilter> getFilters() { + return new ArrayList<TracSearchFilter>(filterByFieldName.values()); + } + + public void setAscending(boolean ascending) { + this.ascending = ascending; + } + + public boolean isAscending() { + return ascending; + } + + public void setOrderBy(String orderBy) { + this.orderBy = orderBy; + } + + public String getOrderBy() { + return orderBy; + } + + /** + * Returns a Trac query string that conforms to the format defined at + * {@link http://projects.edgewall.com/trac/wiki/TracQuery#QueryLanguage}. + * + * @return the empty string, if no search order and criteria are defined; a + * string that starts with &, otherwise + */ + public String toQuery() { + StringBuilder sb = new StringBuilder(); + if (orderBy != null) { + sb.append("&order="); + sb.append(orderBy); + if (!ascending) { + sb.append("&desc=1"); + } + } + for (TracSearchFilter filter : filterByFieldName.values()) { + sb.append("&"); + sb.append(filter.getFieldName()); + sb.append(filter.getOperator().getQueryValue()); + sb.append("="); + List<String> values = filter.getValues(); + for (Iterator<String> it = values.iterator(); it.hasNext();) { + sb.append(it.next()); + if (it.hasNext()) { + sb.append("|"); + } + } + } + return sb.toString(); + } + + /** + * Returns a URL encoded string that can be passed as an argument to the + * Trac query script. + * + * @return the empty string, if no search order and criteria are defined; a + * string that starts with &, otherwise + */ + public String toUrl() { + StringBuilder sb = new StringBuilder(); + if (orderBy != null) { + sb.append("&order="); + sb.append(orderBy); + if (!ascending) { + sb.append("&desc=1"); + } + } else if (filterByFieldName.isEmpty()) { + // TODO figure out why search must be ordered when logged in (otherwise + // no results will be returned) + sb.append("&order=id"); + } + + for (TracSearchFilter filter : filterByFieldName.values()) { + for (String value : filter.getValues()) { + sb.append("&"); + sb.append(filter.getFieldName()); + sb.append("="); + try { + sb.append(URLEncoder.encode(filter.getOperator().getQueryValue(), ITracClient.CHARSET)); + sb.append(URLEncoder.encode(value, ITracClient.CHARSET)); + } catch (UnsupportedEncodingException e) { + MylarStatusHandler.log(e, "Unexpected exception while decoding URL"); + } + } + } + return sb.toString(); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSearchFilter.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSearchFilter.java new file mode 100644 index 000000000..42f5c2942 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSearchFilter.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a search criterion. Each criterion is applied to a field such as + * milestone or priority. It has a compare operator and a list of values. The + * compare mode is <code>OR</code> for the operators <code>contains</code>, + * <code>starts with</code>, <code>ends with</code> and <code>is</code>. + * The compare mode is <code>AND</code> for all other (negated) operators. + * + * @author Steffen Pingel + */ +public class TracSearchFilter { + + public enum CompareOperator { + CONTAINS("~"), CONTAINS_NOT("!~"), BEGINS_WITH("^"), NOT_BEGINS_WITH("!^"), ENDS_WITH("$"), NOT_ENDS_WITH("!$"), IS( + ""), IS_NOT("!"); + + public static CompareOperator fromUrl(String value) { + for (CompareOperator operator : values()) { + if (operator != IS && operator != IS_NOT && value.startsWith(operator.queryValue)) { + return operator; + } + } + if (value.startsWith(IS_NOT.queryValue)) { + return IS_NOT; + } + return IS; + } + + /** The string that represent the operator in a Trac query. */ + private String queryValue; + + CompareOperator(String queryValue) { + this.queryValue = queryValue; + } + + public String getQueryValue() { + return queryValue; + } + + public String toString() { + switch (this) { + case CONTAINS: + return "contains"; + case CONTAINS_NOT: + return "does not contain"; + case BEGINS_WITH: + return "begins with"; + case NOT_BEGINS_WITH: + return "does not begin with"; + case ENDS_WITH: + return "ends with"; + case NOT_ENDS_WITH: + return "does not end with"; + case IS_NOT: + return "is not"; + default: + return "is"; + } + } + + } + + private String fieldName; + + private CompareOperator operator; + + private List<String> values = new ArrayList<String>(); + + public TracSearchFilter(String fieldName) { + this.fieldName = fieldName; + } + + public void addValue(String value) { + values.add(value); + } + + public String getFieldName() { + return fieldName; + } + + public CompareOperator getOperator() { + return operator; + } + + public List<String> getValues() { + return values; + } + + public void setOperator(CompareOperator operator) { + this.operator = operator; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSeverity.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSeverity.java new file mode 100644 index 000000000..7a516f4db --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracSeverity.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +/** + * @author Steffen Pingel + */ +public class TracSeverity extends TracTicketAttribute { + + private static final long serialVersionUID = 2173932517704827316L; + + public TracSeverity(String name, int value) { + super(name, value); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicket.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicket.java new file mode 100644 index 000000000..9c91acd5b --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicket.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.mylar.internal.trac.core.InvalidTicketException; +import org.eclipse.mylar.internal.trac.core.util.TracUtils; + +/** + * Represents a Trac ticket as it is retrieved from a Trac repository. + * + * @author Steffen Pingel + */ +public class TracTicket { + + /** + * Represents the key of a string propertiy of a ticket. + * + * @author Steffen Pingel + */ + public enum Key { + CC("cc"), CHANGE_TIME("changetime"), COMPONENT("component"), DESCRIPTION("description"), ID("id"), KEYWORDS( + "keywords"), MILESTONE("milestone"), OWNER("owner"), PRIORITY("priority"), REPORTER("reporter"), RESOLUTION( + "resolution"), STATUS("status"), SEVERITY("severity"), SUMMARY("summary"), TIME("time"), TYPE("type"), VERSION( + "version"); + + public static Key fromKey(String name) { + for (Key key : Key.values()) { + if (key.getKey().equals(name)) { + return key; + } + } + return null; + } + + private String key; + + Key(String key) { + this.key = key; + } + + public String toString() { + return key; + } + + public String getKey() { + return key; + } + } + + public static final int INVALID_ID = -1; + + private Date created; + + /** + * User defined custom ticket fields. + * + * @see http://projects.edgewall.com/trac/wiki/TracTicketsCustomFields + */ + private Map<String, String> customValueByKey; + + private int id = INVALID_ID; + + private Date lastChanged; + + /** Trac's built-in ticket properties. */ + private Map<Key, String> valueByKey = new HashMap<Key, String>(); + + private List<TracComment> comments; + + private List<TracAttachment> attachments; + + private String[] actions; + + private String[] resolutions; + + public TracTicket() { + } + + /** + * Constructs a Trac ticket. + * + * @param id + * the nummeric Trac ticket id + */ + public TracTicket(int id) { + this.id = id; + } + + public Date getCreated() { + return created; + } + + public int getId() { + return id; + } + + public Date getLastChanged() { + return lastChanged; + } + + public String getCustomValue(String key) { + if (customValueByKey == null) { + return null; + } + return customValueByKey.get(key); + } + + public String getValue(Key key) { + return valueByKey.get(key); + } + + public Map<String, String> getValues() { + Map<String, String> result = new HashMap<String, String>(); + for (Key key : valueByKey.keySet()) { + result.put(key.getKey(), valueByKey.get(key)); + } + if (customValueByKey != null) { + result.putAll(customValueByKey); + } + return result; + } + + public boolean isValid() { + return getId() != TracTicket.INVALID_ID; + } + + public void putBuiltinValue(Key key, String value) throws InvalidTicketException { + valueByKey.put(key, value); + } + + public void putCustomValue(String key, String value) { + if (customValueByKey == null) { + customValueByKey = new HashMap<String, String>(); + } + customValueByKey.put(key, value); + } + + /** + * Stores a value as it is retrieved from the repository. + * + * @throws InvalidTicketException + * thrown if the type of <code>value</code> is not valid + */ + public boolean putValue(String keyName, String value) throws InvalidTicketException { + Key key = Key.fromKey(keyName); + if (key != null) { + if (key == Key.ID || key == Key.TIME || key == Key.CHANGE_TIME) { + return false; + } + putBuiltinValue(key, value); + } else if (value instanceof String) { + putCustomValue(keyName, (String) value); + } else { + throw new InvalidTicketException("Expected string value for custom key '" + keyName + "', got '" + value + + "'"); + } + return true; + } + + public void setCreated(int created) { + this.created = TracUtils.parseDate(created); + } + + public void setId(int id) { + this.id = id; + } + + public void setLastChanged(int lastChanged) { + this.lastChanged = TracUtils.parseDate(lastChanged); + } + + public void addComment(TracComment comment) { + if (comments == null) { + comments = new ArrayList<TracComment>(); + } + comments.add(comment); + } + + public void addAttachment(TracAttachment attachment) { + if (attachments == null) { + attachments = new ArrayList<TracAttachment>(); + } + attachments.add(attachment); + } + + public TracComment[] getComments() { + return (comments != null) ? comments.toArray(new TracComment[0]) : null; + } + + public TracAttachment[] getAttachments() { + return (attachments != null) ? attachments.toArray(new TracAttachment[0]) : null; + } + + public void setActions(String[] actions) { + this.actions = actions; + } + + public String[] getActions() { + return actions; + } + + public void setResolutions(String[] resolutions) { + this.resolutions = resolutions; + } + + public String[] getResolutions() { + return resolutions; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketAttribute.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketAttribute.java new file mode 100644 index 000000000..01e4d5b2d --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketAttribute.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.io.Serializable; + +/** + * @author Steffen Pingel + */ +public class TracTicketAttribute implements Comparable<TracTicketAttribute>, Serializable { + + private static final long serialVersionUID = -8611030780681519787L; + + private String name; + + private int value; + + public TracTicketAttribute(String name, int value) { + this.name = name; + this.value = value; + } + + public int compareTo(TracTicketAttribute o) { + return value - o.value; + } + + public String getName() { + return name; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketResolution.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketResolution.java new file mode 100644 index 000000000..71c863c3e --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketResolution.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +/** + * @author Steffen Pingel + */ +public class TracTicketResolution extends TracTicketAttribute { + + private static final long serialVersionUID = -6933211257044813716L; + + public TracTicketResolution(String name, int value) { + super(name, value); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketStatus.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketStatus.java new file mode 100644 index 000000000..d95baba3f --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketStatus.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +/** + * @author Steffen Pingel + */ +public class TracTicketStatus extends TracTicketAttribute { + + private static final long serialVersionUID = -8844909853931772506L; + + public TracTicketStatus(String name, int value) { + super(name, value); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketType.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketType.java new file mode 100644 index 000000000..c87673fa0 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracTicketType.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +/** + * @author Steffen Pingel + */ +public class TracTicketType extends TracTicketAttribute { + + private static final long serialVersionUID = -3157354751904259304L; + + public TracTicketType(String name, int value) { + super(name, value); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java new file mode 100644 index 000000000..f65a6b007 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.model; + +import java.util.Date; + +/** + * @author Steffen Pingel + */ +public class TracVersion extends TracAttribute { + + private static final long serialVersionUID = 9018237956062697410L; + + private Date time; + + private String description; + + public TracVersion(String name) { + super(name); + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracHttpClientTransportFactory.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracHttpClientTransportFactory.java new file mode 100644 index 000000000..bc94ce5d8 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracHttpClientTransportFactory.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2006 - 2006 Mylar eclipse.org project and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mylar project committers - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.util; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.util.zip.GZIPInputStream; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpVersion; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.xmlrpc.XmlRpcException; +import org.apache.xmlrpc.XmlRpcRequest; +import org.apache.xmlrpc.client.XmlRpcClient; +import org.apache.xmlrpc.client.XmlRpcCommonsTransport; +import org.apache.xmlrpc.client.XmlRpcHttpClientConfig; +import org.apache.xmlrpc.client.XmlRpcTransport; +import org.apache.xmlrpc.client.XmlRpcTransportFactoryImpl; +import org.apache.xmlrpc.util.XmlRpcIOException; +import org.eclipse.mylar.internal.tasks.core.WebClientUtil; + +/** + * A custom transport factory used to establish XML-RPC connections. Uses the + * Mylar proxy settings. + * + * @author Steffen Pingel + */ +public class TracHttpClientTransportFactory extends XmlRpcTransportFactoryImpl { + + public static class TracHttpException extends XmlRpcException { + + private static final long serialVersionUID = 9032521978140685830L; + + public TracHttpException(int responseCode) { + super(responseCode, "HTTP Error " + responseCode); + } + + } + + /** + * A transport that uses the Apache HttpClient library. + */ + public static class TracHttpClientTransport extends XmlRpcCommonsTransport { + + private int contentLength; + private Proxy proxy; + + public TracHttpClientTransport(XmlRpcClient client) { + super(client); + + XmlRpcHttpClientConfig config = (XmlRpcHttpClientConfig) client.getConfig(); + // this needs to be set to avoid exceptions + getHttpClient().getParams().setAuthenticationPreemptive(config.getBasicUserName() != null); + } + + public HttpClient getHttpClient() { + return (HttpClient) getValue("client"); + } + + @Override + protected InputStream getInputStream() throws XmlRpcException { + int responseCode = getMethod().getStatusCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + throw new TracHttpException(responseCode); + } + + return super.getInputStream(); + } + + public PostMethod getMethod() { + return (PostMethod) getValue("method"); + } + + public void setMethod(PostMethod method) { + setValue("method", method); + } + + private Object getValue(String name) { + try { + Field field = XmlRpcCommonsTransport.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(this); + } catch (Throwable t) { + throw new RuntimeException("Internal error accessing HttpClient", t); + } + } + + private void setValue(String name, Object value) { + try { + Field field = XmlRpcCommonsTransport.class.getDeclaredField(name); + field.setAccessible(true); + field.set(this, value); + } catch (Throwable t) { + throw new RuntimeException("Internal error accessing HttpClient", t); + } + } + + /** + * Based on the implementation of XmlRpcCommonsTransport and its super classes. + */ + @Override + public Object sendRequest(XmlRpcRequest pRequest) throws XmlRpcException { + XmlRpcHttpClientConfig config = (XmlRpcHttpClientConfig) pRequest.getConfig(); + + String url = config.getServerURL().toString(); + WebClientUtil.setupHttpClient(getHttpClient(), proxy, url); + + PostMethod method = new PostMethod(WebClientUtil.getRequestPath(url)); + + if (config.getConnectionTimeout() != 0) + getHttpClient().getHttpConnectionManager().getParams().setConnectionTimeout(config.getConnectionTimeout()); + + if (config.getReplyTimeout() != 0) + getHttpClient().getHttpConnectionManager().getParams().setSoTimeout(config.getConnectionTimeout()); + + method.getParams().setVersion(HttpVersion.HTTP_1_1); + + setMethod(method); + + initHttpHeaders(pRequest); + + boolean closed = false; + try { + RequestWriter writer = newRequestWriter(pRequest); + writeRequest(writer); + InputStream istream = getInputStream(); + if (isResponseGzipCompressed(config)) { + istream = new GZIPInputStream(istream); + } + Object result = readResponse(config, istream); + closed = true; + close(); + return result; + } catch (IOException e) { + throw new XmlRpcException("Failed to read servers response: " + + e.getMessage(), e); + } finally { + if (!closed) { try { close(); } catch (Throwable ignore) {} } + } + } + + @Override + protected void writeRequest(final RequestWriter pWriter) throws XmlRpcException { + getMethod().setRequestEntity(new RequestEntity(){ + public boolean isRepeatable() { return true; } + public void writeRequest(OutputStream pOut) throws IOException { + /* Make sure, that the socket is not closed by replacing it with our + * own BufferedOutputStream. + */ + BufferedOutputStream bos = new BufferedOutputStream(pOut){ + public void close() throws IOException { + flush(); + } + }; + try { + Method m = RequestWriter.class.getDeclaredMethod("write", new Class[] { OutputStream.class }); + m.setAccessible(true); + m.invoke(pWriter, bos); + } catch (Exception e) { + throw new XmlRpcIOException(e); + } + } + public long getContentLength() { return contentLength; } + public String getContentType() { return "text/xml"; } + }); + + try { + getHttpClient().executeMethod(getMethod()); + } catch (XmlRpcIOException e) { + Throwable t = e.getLinkedException(); + if (t instanceof XmlRpcException) { + throw (XmlRpcException) t; + } else { + throw new XmlRpcException("Unexpected exception: " + t.getMessage(), t); + } + } catch (IOException e) { + throw new XmlRpcException("I/O error while communicating with HTTP server: " + e.getMessage(), e); + } + } + + @Override + protected void setContentLength(int pLength) { + super.setContentLength(pLength); + + this.contentLength = pLength; + } + + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + public Proxy getProxy() { + return this.proxy; + } + + } + + private final TracHttpClientTransport transport; + + public TracHttpClientTransportFactory(XmlRpcClient client) { + super(client); + + transport = new TracHttpClientTransport(client); + } + + public XmlRpcTransport getTransport() { + return transport; + } + + public void setProxy(Proxy proxy) { + transport.setProxy(proxy); + } + + public Proxy getProxy() { + return transport.getProxy(); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java new file mode 100644 index 000000000..be7b91c55 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2006 Mylar committers and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ + +package org.eclipse.mylar.internal.trac.core.util; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.eclipse.mylar.internal.trac.core.ITracClient; + +/** + * Provides static helper methods. + * + * @author Steffen Pingel + */ +public class TracUtils { + + public static Date parseDate(long seconds) { + Calendar c = Calendar.getInstance(); + c.setTimeZone(TimeZone.getTimeZone(ITracClient.TIME_ZONE)); + c.setTimeInMillis(seconds * 1000l); + return c.getTime(); + } + + public static long toTracTime(Date date) { + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.setTimeZone(TimeZone.getTimeZone(ITracClient.TIME_ZONE)); + return c.getTimeInMillis() / 1000l; + } + +} |