/*******************************************************************************
* Copyright (c) 2014 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.git.servlets;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand.ListMode;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.orion.internal.server.servlets.ServletResourceHandler;
import org.eclipse.orion.server.core.IOUtilities;
import org.eclipse.orion.server.core.LogHelper;
import org.eclipse.orion.server.core.OrionConfiguration;
import org.eclipse.orion.server.core.ProtocolConstants;
import org.eclipse.orion.server.core.ServerStatus;
import org.eclipse.orion.server.core.metastore.ProjectInfo;
import org.eclipse.orion.server.core.metastore.UserInfo;
import org.eclipse.orion.server.core.metastore.WorkspaceInfo;
import org.eclipse.orion.server.git.servlets.GitUtils.Traverse;
import org.eclipse.orion.server.servlets.JsonURIUnqualificationStrategy;
import org.eclipse.orion.server.servlets.OrionServlet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class GitTreeHandlerV1 extends AbstractGitHandler {
GitTreeHandlerV1(ServletResourceHandler<IStatus> statusHandler) {
super(statusHandler);
}
private JSONObject listEntry(String name, long timeStamp, boolean isDir, long length, URI location, String appendName) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put(ProtocolConstants.KEY_NAME, name);
jsonObject.put(ProtocolConstants.KEY_LOCAL_TIMESTAMP, timeStamp);
jsonObject.put(ProtocolConstants.KEY_DIRECTORY, isDir);
jsonObject.put(ProtocolConstants.KEY_LENGTH, length);
if (location != null) {
if (isDir && !location.getPath().endsWith("/")) { //$NON-NLS-1$
location = URIUtil.append(location, ""); //$NON-NLS-1$
}
if (appendName != null) {
if (!appendName.startsWith("/") && !location.getPath().endsWith("/")) //$NON-NLS-1$ //$NON-NLS-2$
appendName = "/" + appendName; //$NON-NLS-1$
location = new URI(location.getScheme(), location.getAuthority(), location.getPath() + appendName, null, location.getFragment());
if (isDir) {
location = URIUtil.append(location, ""); //$NON-NLS-1$
}
}
jsonObject.put(ProtocolConstants.KEY_LOCATION, location);
if (isDir) {
try {
jsonObject.put(ProtocolConstants.KEY_CHILDREN_LOCATION, new URI(location.getScheme(), location.getAuthority(), location.getPath(),
"depth=1", location.getFragment())); //$NON-NLS-1$
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
JSONObject attributes = new JSONObject();
attributes.put("ReadOnly", true); //$NON-NLS-1$
jsonObject.put(ProtocolConstants.KEY_ATTRIBUTES, attributes);
} catch (JSONException e) {
} catch (URISyntaxException e) {
// cannot happen because the key is non-null and the values are strings
throw new RuntimeException(e);
}
return jsonObject;
}
private boolean isAccessAllowed(String userName, ProjectInfo webProject) {
try {
UserInfo user = OrionConfiguration.getMetaStore().readUser(userName);
for (String workspaceId : user.getWorkspaceIds()) {
WorkspaceInfo workspace = OrionConfiguration.getMetaStore().readWorkspace(workspaceId);
if (workspace != null && workspace.getProjectNames().contains(webProject.getFullName()))
return true;
}
} catch (Exception e) {
// fall through and deny access
LogHelper.log(e);
}
return false;
}
@Override
public boolean handleRequest(HttpServletRequest request, HttpServletResponse response, String path) throws ServletException {
String userId = request.getRemoteUser();
if (path.length() == 0) {
if (userId == null) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN, "User name not specified",
null));
return false;
}
try {
UserInfo user = OrionConfiguration.getMetaStore().readUser(userId);
List<String> workspaces = user.getWorkspaceIds();
WorkspaceInfo workspace = OrionConfiguration.getMetaStore().readWorkspace(workspaces.get(0));
URI baseLocation = getURI(request);
URI baseLocationFile = URIUtil.append(baseLocation, "file"); //$NON-NLS-N$
if (workspace != null) {
JSONArray children = new JSONArray();
for (String projectName : workspace.getProjectNames()) {
ProjectInfo project = OrionConfiguration.getMetaStore().readProject(workspace.getUniqueId(), projectName);
if (isAccessAllowed(user.getUserName(), project)) {
IPath projectPath = GitUtils.pathFromProject(workspace, project);
Map<IPath, File> gitDirs = GitUtils.getGitDirs(projectPath, Traverse.GO_DOWN);
for (Map.Entry<IPath, File> entry : gitDirs.entrySet()) {
JSONObject repo = listEntry(entry.getKey().lastSegment(), 0, true, 0, baseLocationFile, entry.getKey().toPortableString());
children.put(repo);
}
}
}
JSONObject result = listEntry("/", 0, true, 0, baseLocation, null); //$NON-NLS-1$
result.put(ProtocolConstants.KEY_CHILDREN, children);
OrionServlet.writeJSONResponse(request, response, result, JsonURIUnqualificationStrategy.ALL_NO_GIT);
return true;
}
} catch (Exception e) {
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"An error occurred while obtaining workspace data.", e));
}
return true;
}
return super.handleRequest(request, response, path);
}
@Override
protected boolean handleGet(RequestInfo requestInfo) throws ServletException {
HttpServletRequest request = requestInfo.request;
HttpServletResponse response = requestInfo.response;
String gitSegment = requestInfo.gitSegment;
Repository repo = requestInfo.db;
String pattern = requestInfo.relativePath;
IPath filePath = requestInfo.filePath;
String meta = request.getParameter("parts"); //$NON-NLS-1$
RevWalk walk = null;
TreeWalk treeWalk = null;
IPath filterPath = new Path(pattern);
try {
if (filterPath.segmentCount() == 0) {
JSONArray children = new JSONArray();
URI baseLocation = getURI(request);
List<Ref> call = new Git(repo).branchList().setListMode(ListMode.ALL).call();
for (Ref ref : call) {
String branchName = Repository.shortenRefName(ref.getName());
JSONObject branch = listEntry(branchName, 0, true, 0, baseLocation, GitUtils.encode(branchName));
children.put(branch);
}
JSONObject result = listEntry(filePath.segment(0), 0, true, 0, baseLocation, null);
result.put(ProtocolConstants.KEY_CHILDREN, children);
OrionServlet.writeJSONResponse(request, response, result, JsonURIUnqualificationStrategy.ALL_NO_GIT);
return true;
}
gitSegment = GitUtils.decode(filterPath.segment(0));
filterPath = filterPath.removeFirstSegments(1);
pattern = filterPath.toPortableString();
ObjectId head = repo.resolve(gitSegment);
if (head == null) {
throw new Exception("Missing ref in git segment");
}
walk = new RevWalk(repo);
// add try catch to catch failures
RevCommit commit = walk.parseCommit(head);
RevTree tree = commit.getTree();
treeWalk = new TreeWalk(repo);
treeWalk.addTree(tree);
treeWalk.setRecursive(false);
if (!pattern.equals("")) { //$NON-NLS-1$
PathFilter pathFilter = PathFilter.create(pattern);
treeWalk.setFilter(pathFilter);
}
JSONArray contents = new JSONArray();
JSONObject result = null;
ArrayList<JSONObject> parents = new ArrayList<JSONObject>();
URI baseLocation = ServletResourceHandler.getURI(request);
Path basePath = new Path(baseLocation.getPath());
IPath tmp = new Path("/"); //$NON-NLS-1$
for (int i = 0; i < 5; i++) {
tmp = tmp.append(basePath.segment(i));
}
URI cloneLocation = new URI(baseLocation.getScheme(), baseLocation.getAuthority(), tmp.toPortableString(), null, baseLocation.getFragment());
JSONObject ref = listEntry(gitSegment, 0, true, 0, cloneLocation, GitUtils.encode(gitSegment));
parents.add(ref);
parents.add(listEntry(new Path(cloneLocation.getPath()).lastSegment(), 0, true, 0, cloneLocation, null));
URI locationWalk = URIUtil.append(cloneLocation, GitUtils.encode(gitSegment));
while (treeWalk.next()) {
if (treeWalk.isSubtree()) {
if (treeWalk.getPathLength() > pattern.length()) {
String name = treeWalk.getNameString();
contents.put(listEntry(name, 0, true, 0, locationWalk, name));
}
if (treeWalk.getPathLength() <= pattern.length()) {
locationWalk = URIUtil.append(locationWalk, treeWalk.getNameString());
parents.add(0, listEntry(treeWalk.getNameString(), 0, true, 0, locationWalk, null));
treeWalk.enterSubtree();
}
} else {
ObjectId objId = treeWalk.getObjectId(0);
ObjectLoader loader = repo.open(objId);
long size = loader.getSize();
if (treeWalk.getPathLength() == pattern.length()) {
if ("meta".equals(meta)) { //$NON-NLS-1$
result = listEntry(treeWalk.getNameString(), 0, false, 0, locationWalk, treeWalk.getNameString());
} else {
return getFileContents(request, response, repo, treeWalk, tree);
}
} else {
String name = treeWalk.getNameString();
contents.put(listEntry(name, 0, false, size, locationWalk, name));
}
}
}
if (result == null) {
result = parents.remove(0);
result.put("Children", contents); //$NON-NLS-1$
}
result.put("Parents", new JSONArray(parents)); //$NON-NLS-1$
response.setContentType("application/json"); //$NON-NLS-1$
response.setHeader("Cache-Control", "no-cache"); //$NON-NLS-1$
response.setHeader("ETag", "\"" + tree.getId().getName() + "\""); //$NON-NLS-1$
OrionServlet.writeJSONResponse(request, response, result);
return true;
} catch (Exception e) {
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"An error occured when requesting commit info.", e));
} finally {
if (walk != null)
walk.release();
if (treeWalk != null)
treeWalk.release();
}
}
private boolean getFileContents(HttpServletRequest request, HttpServletResponse response, Repository repo, TreeWalk treeWalk, RevTree tree) {
ObjectStream stream = null;
try {
ObjectId objId = treeWalk.getObjectId(0);
ObjectLoader loader = repo.open(objId);
response.setHeader("Cache-Control", "no-cache"); //$NON-NLS-1$
response.setHeader("ETag", "\"" + tree.getId().getName() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
response.setContentType("application/octet-stream"); //$NON-NLS-1$
stream = loader.openStream();
IOUtilities.pipe(stream, response.getOutputStream(), true, false);
} catch (MissingObjectException e) {
} catch (IOException e) {
} finally {
try {
if (stream != null)
stream.close();
} catch (IOException e) {
}
}
return true;
}
}