From 93f0b1a11d5a7f7c44cfcb8ff5300bb68b8b8188 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Sat, 01 Oct 2011 17:30:48 -0400
Subject: [PATCH] Refactored federation servlet and utils. Started rpc servlet and utils.

---
 src/com/gitblit/FederationServlet.java                   |  122 ------
 src/com/gitblit/wicket/panels/FederationTokensPanel.java |    8 
 src/com/gitblit/utils/JsonUtils.java                     |  224 +++++++++++++
 src/com/gitblit/utils/RpcUtils.java                      |   86 +++++
 distrib/gitblit.properties                               |    6 
 src/com/gitblit/RpcServlet.java                          |  107 ++++++
 src/com/gitblit/utils/FederationUtils.java               |  225 +++---------
 src/com/gitblit/GitBlit.java                             |    9 
 src/com/gitblit/JsonServlet.java                         |  103 ++++++
 tests/com/gitblit/tests/RpcTests.java                    |   40 ++
 src/WEB-INF/web.xml                                      |   22 +
 tests/com/gitblit/tests/FederationTests.java             |   13 
 src/com/gitblit/Constants.java                           |   25 +
 13 files changed, 692 insertions(+), 298 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index 5493b25..2876382 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -87,6 +87,12 @@
 # SINCE 0.5.0 
 web.allowAdministration = true
 
+# Allows remote clients to list repositories and administer the Gitblit instance
+# if they have administrator permissions.
+#
+# SINCE 0.6.1 
+web.enableRpcServlet = false
+
 # Allow dynamic zip downloads.
 #
 # SINCE 0.5.0   
diff --git a/src/WEB-INF/web.xml b/src/WEB-INF/web.xml
index d557725..0a6cea9 100644
--- a/src/WEB-INF/web.xml
+++ b/src/WEB-INF/web.xml
@@ -69,7 +69,21 @@
 		<url-pattern>/federation/*</url-pattern>
 	</servlet-mapping>	
 	
-
+	
+	<!-- Rpc Servlet
+		 <url-pattern> MUST match: 
+		 	* com.gitblit.Constants.RPC_PATH		 
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>RpcServlet</servlet-name>
+		<servlet-class>com.gitblit.RpcServlet</servlet-class>		
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>RpcServlet</servlet-name>
+		<url-pattern>/rpc/*</url-pattern>
+	</servlet-mapping>	
+	
+	
 	<!-- Git Access Restriction Filter
 		 <url-pattern> MUST match: 
 			* GitServlet
@@ -114,6 +128,7 @@
 		<url-pattern>/zip/*</url-pattern>
 	</filter-mapping>
 		
+		
 	<!-- Wicket Filter -->
     <filter>
         <filter-name>wicketFilter</filter-name>
@@ -136,8 +151,9 @@
              	* Zipfilter <url-pattern>
              	* ZipServlet <url-pattern>
              	* com.gitblit.Constants.ZIP_PATH
-             	* FederationServlet <url-pattern> -->
-            <param-value>git/,feed/,zip/,federation/</param-value>
+             	* FederationServlet <url-pattern>
+             	* RpcServlet <url-pattern> -->
+            <param-value>git/,feed/,zip/,federation/,rpc/</param-value>
         </init-param>
     </filter>
     <filter-mapping>
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index f4541c9..63a877f 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -52,6 +52,8 @@
 	public static final String SYNDICATION_PATH = "/feed/";
 
 	public static final String FEDERATION_PATH = "/federation/";
+	
+	public static final String RPC_PATH = "/rpc/";
 
 	public static final String BORDER = "***********************************************************";
 
@@ -193,4 +195,27 @@
 			return name();
 		}
 	}
+
+	/**
+	 * Enumeration representing the possible remote procedure call requests from
+	 * a client.
+	 */
+	public static enum RpcRequest {
+		LIST_REPOSITORIES, CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY,
+		LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER;
+		
+		public static RpcRequest fromName(String name) {
+			for (RpcRequest type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return LIST_REPOSITORIES;
+		}
+		
+		@Override
+		public String toString() {
+			return name();
+		}
+	}
 }
diff --git a/src/com/gitblit/FederationServlet.java b/src/com/gitblit/FederationServlet.java
index 7dc5d6a..0be1066 100644
--- a/src/com/gitblit/FederationServlet.java
+++ b/src/com/gitblit/FederationServlet.java
@@ -15,7 +15,6 @@
  */
 package com.gitblit;
 
-import java.io.BufferedReader;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -23,24 +22,16 @@
 import java.util.List;
 import java.util.Map;
 
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletResponse;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationToken;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.FederationProposal;
-import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.FederationUtils;
 import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 
 /**
  * Handles federation requests.
@@ -48,67 +39,25 @@
  * @author James Moger
  * 
  */
-public class FederationServlet extends HttpServlet {
+public class FederationServlet extends JsonServlet {
 
 	private static final long serialVersionUID = 1L;
-
-	private transient Logger logger = LoggerFactory.getLogger(FederationServlet.class);
 
 	public FederationServlet() {
 		super();
 	}
 
 	/**
-	 * Returns an url to this servlet for the specified parameters.
-	 * 
-	 * @param sourceURL
-	 *            the url of the source gitblit instance
-	 * @param token
-	 *            the federation token of the source gitblit instance
-	 * @param req
-	 *            the pull type request
-	 */
-	public static String asFederationLink(String sourceURL, String token, FederationRequest req) {
-		return asFederationLink(sourceURL, null, token, req, null);
-	}
-
-	/**
-	 * 
-	 * @param remoteURL
-	 *            the url of the remote gitblit instance
-	 * @param tokenType
-	 *            the type of federation token of a gitblit instance
-	 * @param token
-	 *            the federation token of a gitblit instance
-	 * @param req
-	 *            the pull type request
-	 * @param myURL
-	 *            the url of this gitblit instance
-	 * @return
-	 */
-	public static String asFederationLink(String remoteURL, FederationToken tokenType,
-			String token, FederationRequest req, String myURL) {
-		if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
-			remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
-		}
-		if (req == null) {
-			req = FederationRequest.PULL_REPOSITORIES;
-		}
-		return remoteURL + Constants.FEDERATION_PATH + "?req=" + req.name().toLowerCase()
-				+ (token == null ? "" : ("&token=" + token))
-				+ (tokenType == null ? "" : ("&tokenType=" + tokenType.name().toLowerCase()))
-				+ (myURL == null ? "" : ("&url=" + StringUtils.encodeURL(myURL)));
-	}
-
-	/**
-	 * Returns the list of repositories for federation requests.
+	 * Processes a federation request.
 	 * 
 	 * @param request
 	 * @param response
 	 * @throws javax.servlet.ServletException
 	 * @throws java.io.IOException
 	 */
-	private void processRequest(javax.servlet.http.HttpServletRequest request,
+
+	@Override
+	protected void processRequest(javax.servlet.http.HttpServletRequest request,
 			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
 			java.io.IOException {
 		FederationRequest reqType = FederationRequest.fromName(request.getParameter("req"));
@@ -137,26 +86,11 @@
 
 		if (FederationRequest.PROPOSAL.equals(reqType)) {
 			// Receive a gitblit federation proposal
-			BufferedReader reader = request.getReader();
-			StringBuilder json = new StringBuilder();
-			String line = null;
-			while ((line = reader.readLine()) != null) {
-				json.append(line);
-			}
-			reader.close();
-
-			// check to see if we have proposal data
-			if (json.length() == 0) {
-				logger.error(MessageFormat.format("Failed to receive proposal data from {0}",
-						request.getRemoteAddr()));
-				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+			FederationProposal proposal = deserialize(request, response, FederationProposal.class);
+			if (proposal == null) {
 				return;
 			}
-
-			// deserialize the proposal
-			Gson gson = new Gson();
-			FederationProposal proposal = gson.fromJson(json.toString(), FederationProposal.class);
-
+			
 			// reject proposal, if not receipt prohibited
 			if (!GitBlit.getBoolean(Keys.federation.allowProposals, false)) {
 				logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
@@ -193,25 +127,13 @@
 			String remoteId = StringUtils.decodeFromHtml(request.getParameter("url"));
 			String identification = MessageFormat.format("{0} ({1})", remoteId,
 					request.getRemoteAddr());
-			BufferedReader reader = request.getReader();
-			StringBuilder json = new StringBuilder();
-			String line = null;
-			while ((line = reader.readLine()) != null) {
-				json.append(line);
-			}
-			reader.close();
 
-			// check to see if we have repository data
-			if (json.length() == 0) {
-				logger.error(MessageFormat.format(
-						"Failed to receive pulled repositories list from {0}", identification));
-				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+			// deserialize the status data
+			FederationModel results = deserialize(request, response, FederationModel.class);
+			if (results == null) {
 				return;
 			}
 
-			// deserialize the status data
-			Gson gson = new Gson();
-			FederationModel results = gson.fromJson(json.toString(), FederationModel.class);
 			// setup the last and netx pull dates
 			results.lastPull = new Date();
 			int mins = TimeUtils.convertFrequencyToMinutes(results.frequency);
@@ -279,25 +201,7 @@
 			}
 		}
 
-		if (result != null) {
-			// Send JSON response
-			Gson gson = new GsonBuilder().setPrettyPrinting().create();
-			String json = gson.toJson(result);
-			response.getWriter().append(json);
-		}
-	}
-
-	@Override
-	protected void doPost(javax.servlet.http.HttpServletRequest request,
-			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
-			java.io.IOException {
-		processRequest(request, response);
-	}
-
-	@Override
-	protected void doGet(javax.servlet.http.HttpServletRequest request,
-			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
-			java.io.IOException {
-		processRequest(request, response);
+		// send the result of the request
+		serialize(response, result);
 	}
 }
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index c35340a..e86fcf6 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -63,9 +63,8 @@
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.FederationUtils;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.StringUtils;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 
 /**
  * GitBlit is the servlet context listener singleton that acts as the core for
@@ -978,8 +977,7 @@
 	 */
 	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
 		// convert proposal to json
-		Gson gson = new GsonBuilder().setPrettyPrinting().create();
-		String json = gson.toJson(proposal);
+		String json = JsonUtils.toJsonString(proposal);
 
 		try {
 			// make the proposals folder
@@ -1025,10 +1023,9 @@
 							&& file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
 				}
 			});
-			Gson gson = new Gson();
 			for (File file : files) {
 				String json = com.gitblit.utils.FileUtils.readContent(file, null);
-				FederationProposal proposal = gson.fromJson(json, FederationProposal.class);
+				FederationProposal proposal = JsonUtils.fromJsonString(json, FederationProposal.class);
 				list.add(proposal);
 			}
 		}
diff --git a/src/com/gitblit/JsonServlet.java b/src/com/gitblit/JsonServlet.java
new file mode 100644
index 0000000..b1d1053
--- /dev/null
+++ b/src/com/gitblit/JsonServlet.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Servlet class for interpreting json requests.
+ * 
+ * @author James Moger
+ * 
+ */
+public abstract class JsonServlet extends HttpServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	protected final Logger logger;
+
+	public JsonServlet() {
+		super();
+		logger = LoggerFactory.getLogger(getClass());
+	}
+
+	/**
+	 * Processes an gson request.
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException;
+
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, java.io.IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		processRequest(request, response);
+	}
+
+	protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
+			Class<X> clazz) throws IOException {
+		BufferedReader reader = request.getReader();
+		StringBuilder json = new StringBuilder();
+		String line = null;
+		while ((line = reader.readLine()) != null) {
+			json.append(line);
+		}
+		reader.close();
+
+		if (json.length() == 0) {
+			logger.error(MessageFormat.format("Failed to receive json data from {0}",
+					request.getRemoteAddr()));
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+			return null;
+		}
+
+		Gson gson = new Gson();
+		X object = gson.fromJson(json.toString(), clazz);
+		return object;
+	}
+
+	protected void serialize(HttpServletResponse response, Object o) throws IOException {
+		if (o != null) {
+			// Send JSON response
+			Gson gson = new GsonBuilder().setPrettyPrinting().create();
+			String json = gson.toJson(o);
+			response.getWriter().append(json);
+		}
+	}
+}
diff --git a/src/com/gitblit/RpcServlet.java b/src/com/gitblit/RpcServlet.java
new file mode 100644
index 0000000..83fd733
--- /dev/null
+++ b/src/com/gitblit/RpcServlet.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.HttpUtils;
+
+/**
+ * Handles remote procedure calls.
+ * 
+ * @author James Moger
+ * 
+ */
+public class RpcServlet extends JsonServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	public RpcServlet() {
+		super();
+	}
+
+	/**
+	 * Processes an rpc request.
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	@Override
+	protected void processRequest(javax.servlet.http.HttpServletRequest request,
+			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+			java.io.IOException {
+		RpcRequest reqType = RpcRequest.fromName(request.getParameter("req"));
+		logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType,
+				request.getRemoteAddr()));
+
+		if (!GitBlit.getBoolean(Keys.web.enableRpcServlet, false)) {
+			logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests.");
+			response.sendError(HttpServletResponse.SC_FORBIDDEN);
+			return;
+		}
+
+		// TODO user authentication and authorization
+		UserModel user = null;
+
+		Object result = null;
+		if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) {
+			// list repositories
+
+			// Determine the Gitblit clone url
+			String gitblitUrl = HttpUtils.getGitblitURL(request);
+			StringBuilder sb = new StringBuilder();
+			sb.append(gitblitUrl);
+			sb.append(Constants.GIT_PATH);
+			sb.append("{0}");
+			String cloneUrl = sb.toString();
+
+			List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user);
+			Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+			for (RepositoryModel model : list) {
+				String url = MessageFormat.format(cloneUrl, model.name);
+				repositories.put(url, model);
+			}
+			result = repositories;
+		} else if (RpcRequest.LIST_USERS.equals(reqType)) {
+			// list users
+			if (user == null || !user.canAdmin) {
+				response.sendError(HttpServletResponse.SC_FORBIDDEN);
+				return;
+			}
+			// user is authorized to retrieve all accounts
+			List<String> names = GitBlit.self().getAllUsernames();
+			List<UserModel> users = new ArrayList<UserModel>();
+			for (String name : names) {
+				users.add(GitBlit.self().getUserModel(name));
+			}
+			result = users;
+		}
+
+		// send the result of the request
+		serialize(response, result);
+	}
+}
diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/com/gitblit/utils/FederationUtils.java
index fde9557..d04a7a3 100644
--- a/src/com/gitblit/utils/FederationUtils.java
+++ b/src/com/gitblit/utils/FederationUtils.java
@@ -15,17 +15,7 @@
  */
 package com.gitblit.utils;
 
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
 import java.lang.reflect.Type;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.security.SecureRandom;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -34,28 +24,21 @@
 import java.util.List;
 import java.util.Map;
 
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
 import javax.servlet.http.HttpServletResponse;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants;
 import com.gitblit.Constants.FederationProposalResult;
 import com.gitblit.Constants.FederationRequest;
-import com.gitblit.FederationServlet;
+import com.gitblit.Constants.FederationToken;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
 
 /**
@@ -65,8 +48,6 @@
  * 
  */
 public class FederationUtils {
-
-	public static final String CHARSET;
 
 	public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
 	}.getType();
@@ -80,23 +61,48 @@
 	public static final Type RESULTS_TYPE = new TypeToken<List<FederationModel>>() {
 	}.getType();
 
-	private static final SSLContext SSL_CONTEXT;
-
-	private static final DummyHostnameVerifier HOSTNAME_VERIFIER;
-
 	private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class);
 
-	static {
-		SSLContext context = null;
-		try {
-			context = SSLContext.getInstance("SSL");
-			context.init(null, new TrustManager[] { new DummyTrustManager() }, new SecureRandom());
-		} catch (Throwable t) {
-			t.printStackTrace();
+	/**
+	 * Returns an url to this servlet for the specified parameters.
+	 * 
+	 * @param sourceURL
+	 *            the url of the source gitblit instance
+	 * @param token
+	 *            the federation token of the source gitblit instance
+	 * @param req
+	 *            the pull type request
+	 */
+	public static String asLink(String sourceURL, String token, FederationRequest req) {
+		return asLink(sourceURL, null, token, req, null);
+	}
+
+	/**
+	 * 
+	 * @param remoteURL
+	 *            the url of the remote gitblit instance
+	 * @param tokenType
+	 *            the type of federation token of a gitblit instance
+	 * @param token
+	 *            the federation token of a gitblit instance
+	 * @param req
+	 *            the pull type request
+	 * @param myURL
+	 *            the url of this gitblit instance
+	 * @return
+	 */
+	public static String asLink(String remoteURL, FederationToken tokenType, String token,
+			FederationRequest req, String myURL) {
+		if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
+			remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
 		}
-		SSL_CONTEXT = context;
-		HOSTNAME_VERIFIER = new DummyHostnameVerifier();
-		CHARSET = "UTF-8";
+		if (req == null) {
+			req = FederationRequest.PULL_REPOSITORIES;
+		}
+		return remoteURL + Constants.FEDERATION_PATH + "?req=" + req.name().toLowerCase()
+				+ (token == null ? "" : ("&token=" + token))
+				+ (tokenType == null ? "" : ("&tokenType=" + tokenType.name().toLowerCase()))
+				+ (myURL == null ? "" : ("&url=" + StringUtils.encodeURL(myURL)));
 	}
 
 	/**
@@ -195,10 +201,9 @@
 	 * @return true if there is a route to the remoteUrl
 	 */
 	public static boolean poke(String remoteUrl) throws Exception {
-		String url = FederationServlet.asFederationLink(remoteUrl, null, FederationRequest.POKE);
-		Gson gson = new Gson();
-		String json = gson.toJson("POKE");
-		int status = writeJson(url, json);
+		String url = asLink(remoteUrl, null, FederationRequest.POKE);
+		String json = JsonUtils.toJsonString("POKE");
+		int status = JsonUtils.sendJsonString(url, json);
 		return status == HttpServletResponse.SC_OK;
 	}
 
@@ -213,11 +218,9 @@
 	 */
 	public static FederationProposalResult propose(String remoteUrl, FederationProposal proposal)
 			throws Exception {
-		String url = FederationServlet
-				.asFederationLink(remoteUrl, null, FederationRequest.PROPOSAL);
-		Gson gson = new GsonBuilder().setPrettyPrinting().create();
-		String json = gson.toJson(proposal);
-		int status = writeJson(url, json);
+		String url = asLink(remoteUrl, null, FederationRequest.PROPOSAL);
+		String json = JsonUtils.toJsonString(proposal);
+		int status = JsonUtils.sendJsonString(url, json);
 		switch (status) {
 		case HttpServletResponse.SC_FORBIDDEN:
 			// remote Gitblit Federation disabled
@@ -251,9 +254,9 @@
 	 */
 	public static Map<String, RepositoryModel> getRepositories(FederationModel registration,
 			boolean checkExclusions) throws Exception {
-		String url = FederationServlet.asFederationLink(registration.url, registration.token,
+		String url = asLink(registration.url, registration.token,
 				FederationRequest.PULL_REPOSITORIES);
-		Map<String, RepositoryModel> models = readGson(url, REPOSITORIES_TYPE);
+		Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE);
 		if (checkExclusions) {
 			Map<String, RepositoryModel> includedModels = new HashMap<String, RepositoryModel>();
 			for (Map.Entry<String, RepositoryModel> entry : models.entrySet()) {
@@ -274,9 +277,8 @@
 	 * @throws Exception
 	 */
 	public static Collection<UserModel> getUsers(FederationModel registration) throws Exception {
-		String url = FederationServlet.asFederationLink(registration.url, registration.token,
-				FederationRequest.PULL_USERS);
-		Collection<UserModel> models = readGson(url, USERS_TYPE);
+		String url = asLink(registration.url, registration.token, FederationRequest.PULL_USERS);
+		Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE);
 		return models;
 	}
 
@@ -289,9 +291,8 @@
 	 * @throws Exception
 	 */
 	public static Map<String, String> getSettings(FederationModel registration) throws Exception {
-		String url = FederationServlet.asFederationLink(registration.url, registration.token,
-				FederationRequest.PULL_SETTINGS);
-		Map<String, String> settings = readGson(url, SETTINGS_TYPE);
+		String url = asLink(registration.url, registration.token, FederationRequest.PULL_SETTINGS);
+		Map<String, String> settings = JsonUtils.retrieveJson(url, SETTINGS_TYPE);
 		return settings;
 	}
 
@@ -309,122 +310,10 @@
 	 */
 	public static boolean acknowledgeStatus(String identification, FederationModel registration)
 			throws Exception {
-		String url = FederationServlet.asFederationLink(registration.url, null, registration.token,
-				FederationRequest.STATUS, identification);
-		Gson gson = new GsonBuilder().setPrettyPrinting().create();
-		String json = gson.toJson(registration);
-		int status = writeJson(url, json);
+		String url = asLink(registration.url, null, registration.token, FederationRequest.STATUS,
+				identification);
+		String json = JsonUtils.toJsonString(registration);
+		int status = JsonUtils.sendJsonString(url, json);
 		return status == HttpServletResponse.SC_OK;
-	}
-
-	/**
-	 * Reads a gson object from the specified url.
-	 * 
-	 * @param url
-	 * @param type
-	 * @return
-	 * @throws Exception
-	 */
-	public static <X> X readGson(String url, Type type) throws Exception {
-		String json = readJson(url);
-		if (StringUtils.isEmpty(json)) {
-			return null;
-		}
-		Gson gson = new Gson();
-		return gson.fromJson(json, type);
-	}
-
-	/**
-	 * Reads a JSON response.
-	 * 
-	 * @param url
-	 * @return the JSON response as a string
-	 * @throws Exception
-	 */
-	public static String readJson(String url) throws Exception {
-		URL urlObject = new URL(url);
-		URLConnection conn = urlObject.openConnection();
-		conn.setRequestProperty("Accept-Charset", CHARSET);
-		conn.setUseCaches(false);
-		conn.setDoInput(true);
-		if (conn instanceof HttpsURLConnection) {
-			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
-			secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
-			secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
-		}
-		InputStream is = conn.getInputStream();
-		BufferedReader reader = new BufferedReader(new InputStreamReader(is, CHARSET));
-		StringBuilder json = new StringBuilder();
-		char[] buffer = new char[4096];
-		int len = 0;
-		while ((len = reader.read(buffer)) > -1) {
-			json.append(buffer, 0, len);
-		}
-		is.close();
-		return json.toString();
-	}
-
-	/**
-	 * Writes a JSON message to the specified url.
-	 * 
-	 * @param url
-	 *            the url to write to
-	 * @param json
-	 *            the json message to send
-	 * @return the http request result code
-	 * @throws Exception
-	 */
-	public static int writeJson(String url, String json) throws Exception {
-		byte[] jsonBytes = json.getBytes(CHARSET);
-		URL urlObject = new URL(url);
-		URLConnection conn = urlObject.openConnection();
-		conn.setRequestProperty("Content-Type", "text/plain;charset=" + CHARSET);
-		conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
-		conn.setUseCaches(false);
-		conn.setDoOutput(true);
-		if (conn instanceof HttpsURLConnection) {
-			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
-			secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
-			secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
-		}
-
-		// write json body
-		OutputStream os = conn.getOutputStream();
-		os.write(jsonBytes);
-		os.close();
-
-		int status = ((HttpURLConnection) conn).getResponseCode();
-		return status;
-	}
-
-	/**
-	 * DummyTrustManager trusts all certificates.
-	 */
-	private static class DummyTrustManager implements X509TrustManager {
-
-		@Override
-		public void checkClientTrusted(X509Certificate[] certs, String authType)
-				throws CertificateException {
-		}
-
-		@Override
-		public void checkServerTrusted(X509Certificate[] certs, String authType)
-				throws CertificateException {
-		}
-
-		@Override
-		public X509Certificate[] getAcceptedIssuers() {
-			return null;
-		}
-	}
-
-	/**
-	 * Trusts all hostnames from a certificate, including self-signed certs.
-	 */
-	private static class DummyHostnameVerifier implements HostnameVerifier {
-		@Override
-		public boolean verify(String hostname, SSLSession session) {
-			return true;
-		}
 	}
 }
diff --git a/src/com/gitblit/utils/JsonUtils.java b/src/com/gitblit/utils/JsonUtils.java
new file mode 100644
index 0000000..a697b7a
--- /dev/null
+++ b/src/com/gitblit/utils/JsonUtils.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Utility methods for gson calls to a Gitblit server.
+ * 
+ * @author James Moger
+ * 
+ */
+public class JsonUtils {
+
+	public static final String CHARSET;
+
+	public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
+	}.getType();
+
+	public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
+	}.getType();
+
+	private static final SSLContext SSL_CONTEXT;
+
+	private static final DummyHostnameVerifier HOSTNAME_VERIFIER;
+
+	static {
+		SSLContext context = null;
+		try {
+			context = SSLContext.getInstance("SSL");
+			context.init(null, new TrustManager[] { new DummyTrustManager() }, new SecureRandom());
+		} catch (Throwable t) {
+			t.printStackTrace();
+		}
+		SSL_CONTEXT = context;
+		HOSTNAME_VERIFIER = new DummyHostnameVerifier();
+		CHARSET = "UTF-8";
+	}
+
+	/**
+	 * Creates JSON from the specified object.
+	 * 
+	 * @param o
+	 * @return json
+	 */
+	public static String toJsonString(Object o) {
+		Gson gson = new GsonBuilder().setPrettyPrinting().create();
+		String json = gson.toJson(o);
+		return json;
+	}
+
+	/**
+	 * Convert a json string to an object of the specified type.
+	 * 
+	 * @param json
+	 * @param clazz
+	 * @return an object
+	 */
+	public static <X> X fromJsonString(String json, Class<X> clazz) {
+		Gson gson = new Gson();
+		return gson.fromJson(json, clazz);
+	}
+	
+	/**
+	 * Convert a json string to an object of the specified type.
+	 * 
+	 * @param json
+	 * @param clazz
+	 * @return an object
+	 */
+	public static <X> X fromJsonString(String json, Type type) {
+		Gson gson = new Gson();
+		return gson.fromJson(json, type);
+	}
+
+	/**
+	 * Reads a gson object from the specified url.
+	 * 
+	 * @param url
+	 * @param type
+	 * @return
+	 * @throws Exception
+	 */
+	public static <X> X retrieveJson(String url, Type type) throws Exception {
+		String json = retrieveJsonString(url);
+		if (StringUtils.isEmpty(json)) {
+			return null;
+		}
+		Gson gson = new Gson();
+		return gson.fromJson(json, type);
+	}
+
+	/**
+	 * Retrieves a JSON message.
+	 * 
+	 * @param url
+	 * @return the JSON message as a string
+	 * @throws Exception
+	 */
+	public static String retrieveJsonString(String url) throws Exception {
+		URL urlObject = new URL(url);
+		URLConnection conn = urlObject.openConnection();
+		conn.setRequestProperty("Accept-Charset", CHARSET);
+		conn.setUseCaches(false);
+		conn.setDoInput(true);
+		if (conn instanceof HttpsURLConnection) {
+			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
+			secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
+			secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
+		}
+		InputStream is = conn.getInputStream();
+		BufferedReader reader = new BufferedReader(new InputStreamReader(is, CHARSET));
+		StringBuilder json = new StringBuilder();
+		char[] buffer = new char[4096];
+		int len = 0;
+		while ((len = reader.read(buffer)) > -1) {
+			json.append(buffer, 0, len);
+		}
+		is.close();
+		return json.toString();
+	}
+
+	/**
+	 * Sends a JSON message.
+	 * 
+	 * @param url
+	 *            the url to write to
+	 * @param json
+	 *            the json message to send
+	 * @return the http request result code
+	 * @throws Exception
+	 */
+	public static int sendJsonString(String url, String json) throws Exception {
+		byte[] jsonBytes = json.getBytes(CHARSET);
+		URL urlObject = new URL(url);
+		URLConnection conn = urlObject.openConnection();
+		conn.setRequestProperty("Content-Type", "text/plain;charset=" + CHARSET);
+		conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
+		conn.setUseCaches(false);
+		conn.setDoOutput(true);
+		if (conn instanceof HttpsURLConnection) {
+			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
+			secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
+			secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
+		}
+
+		// write json body
+		OutputStream os = conn.getOutputStream();
+		os.write(jsonBytes);
+		os.close();
+
+		int status = ((HttpURLConnection) conn).getResponseCode();
+		return status;
+	}
+
+	/**
+	 * DummyTrustManager trusts all certificates.
+	 */
+	private static class DummyTrustManager implements X509TrustManager {
+
+		@Override
+		public void checkClientTrusted(X509Certificate[] certs, String authType)
+				throws CertificateException {
+		}
+
+		@Override
+		public void checkServerTrusted(X509Certificate[] certs, String authType)
+				throws CertificateException {
+		}
+
+		@Override
+		public X509Certificate[] getAcceptedIssuers() {
+			return null;
+		}
+	}
+
+	/**
+	 * Trusts all hostnames from a certificate, including self-signed certs.
+	 */
+	private static class DummyHostnameVerifier implements HostnameVerifier {
+		@Override
+		public boolean verify(String hostname, SSLSession session) {
+			return true;
+		}
+	}
+}
diff --git a/src/com/gitblit/utils/RpcUtils.java b/src/com/gitblit/utils/RpcUtils.java
new file mode 100644
index 0000000..919c7bb
--- /dev/null
+++ b/src/com/gitblit/utils/RpcUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Utility methods for rpc calls.
+ * 
+ * @author James Moger
+ * 
+ */
+public class RpcUtils {
+
+	public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
+	}.getType();
+
+	public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
+	}.getType();
+
+	/**
+	 * 
+	 * @param remoteURL
+	 *            the url of the remote gitblit instance
+	 * @param req
+	 *            the rpc request type
+	 * @return
+	 */
+	public static String asLink(String remoteURL, RpcRequest req) {
+		if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
+			remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
+		}
+		if (req == null) {
+			req = RpcRequest.LIST_REPOSITORIES;
+		}
+		return remoteURL + Constants.RPC_PATH + "?req=" + req.name().toLowerCase();
+	}
+	
+	/**
+	 * Retrieves a map of the repositories at the remote gitblit instance keyed
+	 * by the repository clone url.
+	 * 
+	 * @param serverUrl
+	 * @return a map of cloneable repositories
+	 * @throws Exception
+	 */
+	public static Map<String, RepositoryModel> getRepositories(String serverUrl) throws Exception {
+		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORIES);
+		Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE);
+		return models;
+	}
+
+	/**
+	 * Tries to pull the gitblit user accounts from the remote gitblit instance.
+	 * 
+	 * @param serverUrl
+	 * @return a collection of UserModel objects
+	 * @throws Exception
+	 */
+	public static Collection<UserModel> getUsers(String serverUrl) throws Exception {
+		String url = asLink(serverUrl, RpcRequest.LIST_USERS);
+		Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE);
+		return models;
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.java b/src/com/gitblit/wicket/panels/FederationTokensPanel.java
index 3a77188..3454492 100644
--- a/src/com/gitblit/wicket/panels/FederationTokensPanel.java
+++ b/src/com/gitblit/wicket/panels/FederationTokensPanel.java
@@ -28,9 +28,9 @@
 
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.Constants.FederationToken;
-import com.gitblit.FederationServlet;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
+import com.gitblit.utils.FederationUtils;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.pages.SendProposalPage;
 
@@ -42,11 +42,11 @@
 		super(wicketId);
 
 		final String baseUrl = WicketUtils.getGitblitURL(getRequest());
-		add(new ExternalLink("federatedUsers", FederationServlet.asFederationLink(baseUrl, GitBlit.self()
+		add(new ExternalLink("federatedUsers", FederationUtils.asLink(baseUrl, GitBlit.self()
 				.getFederationToken(FederationToken.USERS_AND_REPOSITORIES),
 				FederationRequest.PULL_USERS)));
 
-		add(new ExternalLink("federatedSettings", FederationServlet.asFederationLink(baseUrl, GitBlit
+		add(new ExternalLink("federatedSettings", FederationUtils.asLink(baseUrl, GitBlit
 				.self().getFederationToken(FederationToken.ALL), FederationRequest.PULL_SETTINGS)));
 
 		final List<String[]> data = new ArrayList<String[]>();
@@ -83,7 +83,7 @@
 				}
 				item.add(new Label("value", entry[1]));
 
-				item.add(new ExternalLink("repositoryDefinitions", FederationServlet.asFederationLink(
+				item.add(new ExternalLink("repositoryDefinitions", FederationUtils.asLink(
 						baseUrl, entry[1], FederationRequest.PULL_REPOSITORIES)));
 
 				item.add(new BookmarkablePageLink<Void>("send",
diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
index c5f7e8d..8af3167 100644
--- a/tests/com/gitblit/tests/FederationTests.java
+++ b/tests/com/gitblit/tests/FederationTests.java
@@ -27,12 +27,11 @@
 import com.gitblit.Constants.FederationProposalResult;
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.Constants.FederationToken;
-import com.gitblit.FederationServlet;
 import com.gitblit.GitBlitServer;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.FederationUtils;
-import com.google.gson.Gson;
+import com.gitblit.utils.JsonUtils;
 
 public class FederationTests extends TestCase {
 
@@ -66,10 +65,8 @@
 	}
 
 	public void testDeserialization() throws Exception {
-		String json = "{\"https://localhost:8443/git/a.b.c.orphan.git\":{\"name\":\"a.b.c.orphan.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 3:15:07 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/jgit.git\":{\"name\":\"test/jgit.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 13, 2011 9:42:33 AM\",\"hasCommits\":true,\"showRemoteBranches\":true,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/helloworld.git\":{\"name\":\"test/helloworld.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/working/ticgit\":{\"name\":\"working/ticgit\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 10:35:27 AM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/ticgit.git\":{\"name\":\"ticgit.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 10:35:27 AM\",\"hasCommits\":true,\"showRemoteBranches\":true,\"useTickets\":true,\"useDocs\":true,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/helloworld.git\":{\"name\":\"helloworld.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/helloworld3.git\":{\"name\":\"test/helloworld3.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/bluez-gnome.git\":{\"name\":\"test/bluez-gnome.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Dec 19, 2008 6:35:33 AM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false}}";
-		Gson gson = new Gson();
-		Map<String, RepositoryModel> models = gson
-				.fromJson(json, FederationUtils.REPOSITORIES_TYPE);
+		String json = "{\"https://localhost:8443/git/a.b.c.orphan.git\":{\"name\":\"a.b.c.orphan.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 3:15:07 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/jgit.git\":{\"name\":\"test/jgit.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 13, 2011 9:42:33 AM\",\"hasCommits\":true,\"showRemoteBranches\":true,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/helloworld.git\":{\"name\":\"test/helloworld.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/working/ticgit\":{\"name\":\"working/ticgit\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 10:35:27 AM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/ticgit.git\":{\"name\":\"ticgit.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 22, 2011 10:35:27 AM\",\"hasCommits\":true,\"showRemoteBranches\":true,\"useTickets\":true,\"useDocs\":true,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/helloworld.git\":{\"name\":\"helloworld.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/helloworld3.git\":{\"name\":\"test/helloworld3.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Jul 15, 2008 7:26:48 PM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false},\"https://localhost:8443/git/test/bluez-gnome.git\":{\"name\":\"test/bluez-gnome.git\",\"description\":\"\",\"owner\":\"\",\"lastChange\":\"Dec 19, 2008 6:35:33 AM\",\"hasCommits\":true,\"showRemoteBranches\":false,\"useTickets\":false,\"useDocs\":false,\"accessRestriction\":\"NONE\",\"isFrozen\":false,\"showReadme\":false,\"isFederated\":false}}";		
+		Map<String, RepositoryModel> models = JsonUtils.fromJsonString(json, FederationUtils.REPOSITORIES_TYPE);
 		assertEquals(8, models.size());
 	}
 
@@ -99,9 +96,9 @@
 
 	public void testPullRepositories() throws Exception {
 		try {
-			String url = FederationServlet.asFederationLink("http://localhost:" + port,
+			String url = FederationUtils.asLink("http://localhost:" + port,
 					"testtoken", FederationRequest.PULL_REPOSITORIES);
-			String json = FederationUtils.readJson(url);
+			String json = JsonUtils.retrieveJsonString(url);
 		} catch (IOException e) {
 			if (!e.getMessage().contains("403")) {
 				throw e;
diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java
new file mode 100644
index 0000000..e140fb4
--- /dev/null
+++ b/tests/com/gitblit/tests/RpcTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.IOException;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.RpcUtils;
+
+public class RpcTests extends TestCase {
+
+	public void testListRepositories() throws Exception {
+		Map<String, RepositoryModel> map = null;
+		try {
+			map = RpcUtils.getRepositories("https://localhost:8443");
+		} catch (IOException e) {
+			if (!e.getMessage().contains("403")) {
+				throw e;
+			}
+		}
+		assertTrue("Repository list is null!", map != null);
+		assertTrue("Repository list is empty!", map.size() > 0);
+	}
+}

--
Gitblit v1.9.1