Package org.eclipse.orion.server.git.servlets

Source Code of org.eclipse.orion.server.git.servlets.GitDiffHandlerV1

/*******************************************************************************
* Copyright (c) 2011, 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.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jgit.api.ApplyCommand;
import org.eclipse.jgit.api.ApplyResult;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.api.errors.PatchFormatException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.eclipse.orion.internal.server.servlets.ServletResourceHandler;
import org.eclipse.orion.server.core.IOUtilities;
import org.eclipse.orion.server.core.ProtocolConstants;
import org.eclipse.orion.server.core.ServerStatus;
import org.eclipse.orion.server.core.resources.UniversalUniqueIdentifier;
import org.eclipse.orion.server.git.BaseToCloneConverter;
import org.eclipse.orion.server.git.GitConstants;
import org.eclipse.orion.server.git.jobs.DiffCommand;
import org.eclipse.orion.server.git.objects.Diff;
import org.eclipse.orion.server.servlets.JsonURIUnqualificationStrategy;
import org.eclipse.orion.server.servlets.OrionServlet;
import org.eclipse.osgi.util.NLS;
import org.json.JSONArray;
import org.json.JSONObject;

/**
* A handler for Git Diff operation.
*/
public class GitDiffHandlerV1 extends AbstractGitHandler {

  /**
   * The end of line sequence expected by HTTP.
   */
  private static final String EOL = "\r\n"; //$NON-NLS-1$

  private HttpClient httpClient;

  GitDiffHandlerV1(ServletResourceHandler<IStatus> statusHandler) {
    super(statusHandler);
  }

  @Override
  protected boolean handleGet(RequestInfo requestInfo) throws ServletException {
    String gitSegment = requestInfo.gitSegment;
    HttpServletRequest request = requestInfo.request;
    HttpServletResponse response = requestInfo.response;
    Repository db = requestInfo.db;
    String relativePath = requestInfo.relativePath;
    try {
      String parts = request.getParameter("parts"); //$NON-NLS-1$
      String pattern = relativePath;
      pattern = pattern.isEmpty() ? null : pattern;
      if (parts == null || "uris,diff".equals(parts) || "diff,uris".equals(parts)) //$NON-NLS-1$ //$NON-NLS-2$
        return handleMultiPartGet(request, response, db, gitSegment, pattern);
      if ("uris".equals(parts)) { //$NON-NLS-1$
        OrionServlet.writeJSONResponse(request, response, new Diff(getURI(request), db).toJSON(), JsonURIUnqualificationStrategy.ALL_NO_GIT);
        return true;
      }
      if ("diff".equals(parts)) //$NON-NLS-1$
        return handleGetDiff(request, response, db, gitSegment, pattern, response.getOutputStream());
      if ("diffs".equals(parts)) //$NON-NLS-1$
        return handleGetDiffs(request, response, db, gitSegment, pattern);
      return false; // unknown part
    } catch (Exception e) {
      return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          "An error occured while getting a diff.", e));
    }
  }

  private boolean handleGetDiffs(HttpServletRequest request, HttpServletResponse response, Repository db, String scope, String pattern) throws Exception {
    DiffCommand command = getDiff(request, response, db, scope, pattern, NullOutputStream.INSTANCE);
    if (command == null)
      return true;

    List<DiffEntry> l = command.call();
    JSONArray diffs = new JSONArray();
    URI diffLocation = getURI(request);
    if (pattern != null) {
      IPath patternPath = new Path(pattern);
      IPath diffPath = new Path(diffLocation.getPath());
      diffPath = diffPath.removeLastSegments(patternPath.segmentCount());
      diffLocation = new URI(diffLocation.getScheme(), diffLocation.getAuthority(), diffPath.toPortableString(), null, null);
    }
    URI cloneLocation = BaseToCloneConverter.getCloneLocation(diffLocation, BaseToCloneConverter.DIFF);

    int page = request.getParameter("page") != null ? new Integer(request.getParameter("page")).intValue() : 0; //$NON-NLS-1$ //$NON-NLS-2$
    int pageSize = request.getParameter("pageSize") != null ? new Integer(request.getParameter("pageSize")).intValue() : Integer.MAX_VALUE; //$NON-NLS-1$ //$NON-NLS-2$
    int start = pageSize * (page - 1);
    int end = Math.min(pageSize + start, l.size());
    int i = start;
    for (i = start; i < end; i++) {
      DiffEntry entr = l.get(i);
      JSONObject diff = new JSONObject();
      diff.put(ProtocolConstants.KEY_TYPE, org.eclipse.orion.server.git.objects.Diff.TYPE);
      diff.put(GitConstants.KEY_COMMIT_DIFF_NEWPATH, entr.getNewPath());
      diff.put(GitConstants.KEY_COMMIT_DIFF_OLDPATH, entr.getOldPath());
      diff.put(GitConstants.KEY_COMMIT_DIFF_CHANGETYPE, entr.getChangeType().toString());

      // add diff location for the commit
      String path = entr.getChangeType() != ChangeType.DELETE ? entr.getNewPath() : entr.getOldPath();
      diff.put(GitConstants.KEY_DIFF, createDiffLocation(diffLocation, path));
      diff.put(ProtocolConstants.KEY_CONTENT_LOCATION, createContentLocation(cloneLocation, entr, path));

      diffs.put(diff);
    }

    JSONObject result = new JSONObject();
    result.put(ProtocolConstants.KEY_TYPE, org.eclipse.orion.server.git.objects.Diff.TYPE);
    result.put(ProtocolConstants.KEY_CHILDREN, diffs);
    result.put(ProtocolConstants.KEY_LENGTH, l.size());
    if (i < l.size()) {
      URI nextLocation = new URI(diffLocation.getScheme(), diffLocation.getUserInfo(), diffLocation.getHost(), diffLocation.getPort(),
          diffLocation.getPath(), "pageSize=" + pageSize + "&page=" + (page + 1), diffLocation.getFragment()); //$NON-NLS-1$ //$NON-NLS-2$
      result.put(ProtocolConstants.KEY_NEXT_LOCATION, nextLocation);
    }
    OrionServlet.writeJSONResponse(request, response, result, JsonURIUnqualificationStrategy.ALL_NO_GIT);

    return true;
  }

  private URI createDiffLocation(URI diffLocation, String path) throws URISyntaxException {
    if (path == null)
      return diffLocation;
    IPath diffPath = new Path(diffLocation.getPath());
    diffPath = diffPath.append(path);
    return new URI(diffLocation.getScheme(), diffLocation.getAuthority(), diffPath.toString(), null, null);
  }

  private URI createContentLocation(URI cloneLocation, final DiffEntry entr, String path) throws URISyntaxException {
    // remove /gitapi/clone from the start of path
    IPath clonePath = new Path(cloneLocation.getPath()).removeFirstSegments(2);
    IPath result;
    if (path == null) {
      result = clonePath;
    } else {
      // need to start from the project root
      // project path is of the form /file/{workspaceId}/{projectName}
      result = clonePath.uptoSegment(3).append(path);
    }
    return new URI(cloneLocation.getScheme(), cloneLocation.getUserInfo(), cloneLocation.getHost(), cloneLocation.getPort(), result.makeAbsolute()
        .toString(), cloneLocation.getQuery(), cloneLocation.getFragment());
  }

  private boolean handleGetDiff(HttpServletRequest request, HttpServletResponse response, Repository db, String scope, String pattern, OutputStream out)
      throws Exception {
    DiffCommand command = getDiff(request, response, db, scope, pattern, new BufferedOutputStream(out));
    if (command != null)
      command.call();
    return true;
  }

  private DiffCommand getDiff(HttpServletRequest request, HttpServletResponse response, Repository db, String scope, String pattern, OutputStream out)
      throws Exception {
    boolean ignoreWS = Boolean.parseBoolean(request.getParameter("ignoreWS")); //$NON-NLS-1$
    // Git git = new Git(db);
    DiffCommand diff = new DiffCommand(db);
    diff.setOutputStream(out);
    diff.setIgnoreWhiteSpace(ignoreWS);
    AbstractTreeIterator oldTree;
    AbstractTreeIterator newTree = new FileTreeIterator(db);
    if (scope.contains("..")) { //$NON-NLS-1$
      String[] commits = scope.split("\\.\\."); //$NON-NLS-1$
      if (commits.length != 2) {
        String msg = NLS.bind("Failed to generate diff for {0}", scope);
        statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, msg, null));
        return null;
      }
      oldTree = getTreeIterator(db, commits[0]);
      newTree = getTreeIterator(db, commits[1]);
    } else if (scope.equals(GitConstants.KEY_DIFF_CACHED)) {
      ObjectId head = db.resolve(Constants.HEAD + "^{tree}"); //$NON-NLS-1$
      if (head == null) {
        String msg = NLS.bind("Failed to generate diff for {0}, no HEAD", scope);
        statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, msg, null));
        return null;
      }
      CanonicalTreeParser p = new CanonicalTreeParser();
      ObjectReader reader = db.newObjectReader();
      try {
        p.reset(reader, head);
      } finally {
        reader.release();
      }
      oldTree = p;
      newTree = new DirCacheIterator(db.readDirCache());
    } else if (scope.equals(GitConstants.KEY_DIFF_DEFAULT)) {
      oldTree = new DirCacheIterator(db.readDirCache());
    } else {
      oldTree = getTreeIterator(db, scope);
    }

    String[] paths = request.getParameterValues(ProtocolConstants.KEY_PATH);
    TreeFilter filter = null;
    TreeFilter pathFilter = null;
    if (paths != null) {
      if (paths.length > 1) {
        Set<TreeFilter> pathFilters = new HashSet<TreeFilter>(paths.length);
        for (String path : paths) {
          pathFilters.add(PathFilter.create(path));
        }
        pathFilter = OrTreeFilter.create(pathFilters);
      } else if (paths.length == 1) {
        pathFilter = PathFilter.create(paths[0]);
      }
    }
    if (pattern != null) {
      PathFilter patternFilter = PathFilter.create(pattern);
      if (pathFilter != null)
        filter = AndTreeFilter.create(patternFilter, pathFilter);
      else
        filter = patternFilter;
    } else {
      filter = pathFilter;
    }
    if (filter != null)
      diff.setPathFilter(filter);

    diff.setOldTree(oldTree);
    diff.setNewTree(newTree);
    return diff;
  }

  private boolean handleMultiPartGet(HttpServletRequest request, HttpServletResponse response, Repository db, String scope, String pattern) throws Exception {
    String boundary = createBoundaryString();
    response.setHeader(ProtocolConstants.HEADER_CONTENT_TYPE, "multipart/related; boundary=\"" + boundary + '"'); //$NON-NLS-1$
    OutputStream outputStream = response.getOutputStream();
    Writer out = new OutputStreamWriter(outputStream);
    try {
      out.write("--" + boundary + EOL); //$NON-NLS-1$
      out.write(ProtocolConstants.HEADER_CONTENT_TYPE + ": " + ProtocolConstants.CONTENT_TYPE_JSON + EOL + EOL); //$NON-NLS-1$
      out.flush();
      JSONObject getURIs = new Diff(getURI(request), db).toJSON();
      JsonURIUnqualificationStrategy.ALL.run(request, getURIs);

      out.write(getURIs.toString());
      out.write(EOL + "--" + boundary + EOL); //$NON-NLS-1$
      out.write(ProtocolConstants.HEADER_CONTENT_TYPE + ": plain/text" + EOL + EOL); //$NON-NLS-1$
      out.flush();
      handleGetDiff(request, response, db, scope, pattern, outputStream);
      out.write(EOL);
      out.flush();
    } finally {
      IOUtilities.safeClose(out);
    }
    return true;
  }

  private String createBoundaryString() {
    return new UniversalUniqueIdentifier().toBase64String();
  }

  @Override
  protected boolean handlePost(RequestInfo requestInfo) throws ServletException {
    HttpServletRequest request = requestInfo.request;
    HttpServletResponse response = requestInfo.response;
    Repository db = requestInfo.db;
    String contentType = request.getHeader(ProtocolConstants.HEADER_CONTENT_TYPE);
    if (contentType.startsWith("multipart")) {//$NON-NLS-1$
      return applyPatch(request, response, db, contentType);
    } else {
      return identifyNewDiffResource(request, response);
    }
  }

  private String stripGlobalPaths(Repository db, String message) {
    return message.replaceAll("(?i)" + Pattern.quote(db.getDirectory().getParentFile().getAbsolutePath().toLowerCase()), "");
  }

  private boolean applyPatch(HttpServletRequest request, HttpServletResponse response, Repository db, String contentType) throws ServletException {
    try {
      String patch = readPatch(request.getInputStream(), contentType);
      Git git = new Git(db);
      ApplyCommand applyCommand = git.apply();
      applyCommand.setPatch(IOUtilities.toInputStream(patch));
      // TODO: ignore all errors for now, see bug 366008
      try {
        ApplyResult applyResult = applyCommand.call();
        JSONObject resp = new JSONObject();
        JSONArray modifiedFieles = new JSONArray();
        for (File file : applyResult.getUpdatedFiles()) {
          modifiedFieles.put(stripGlobalPaths(db, file.getAbsolutePath()));
        }
        resp.put("modifiedFieles", modifiedFieles);
        return statusHandler.handleRequest(request, response, new ServerStatus(Status.OK_STATUS, HttpServletResponse.SC_OK, resp));
        // OrionServlet.writeJSONResponse(request, response, toJSON(applyResult));
      } catch (PatchFormatException e) {
        return statusHandler.handleRequest(request, response,
            new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, stripGlobalPaths(db, e.getMessage()), null));
      } catch (PatchApplyException e) {
        return statusHandler.handleRequest(request, response,
            new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, stripGlobalPaths(db, e.getMessage()), null));
      }
    } catch (Exception e) {
      return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          "An error occured when reading the patch.", e));
    }
  }

  private boolean identifyNewDiffResource(HttpServletRequest request, HttpServletResponse response) throws ServletException {
    try {
      StringWriter writer = new StringWriter();
      IOUtilities.pipe(request.getReader(), writer, false, false);
      JSONObject requestObject = new JSONObject(writer.toString());
      URI u = getURI(request);
      IPath p = new Path(u.getPath());
      IPath np = new Path("/"); //$NON-NLS-1$
      for (int i = 0; i < p.segmentCount(); i++) {
        String s = p.segment(i);
        if (i == 2) {
          s += ".."; //$NON-NLS-1$
          s += GitUtils.encode(requestObject.getString(GitConstants.KEY_COMMIT_NEW));
        }
        np = np.append(s);
      }
      if (p.hasTrailingSeparator())
        np = np.addTrailingSeparator();
      URI nu = new URI(u.getScheme(), u.getUserInfo(), u.getHost(), u.getPort(), np.toString(), u.getQuery(), u.getFragment());
      JSONObject result = new JSONObject();
      result.put(ProtocolConstants.KEY_LOCATION, nu.toString());
      OrionServlet.writeJSONResponse(request, response, result, JsonURIUnqualificationStrategy.ALL_NO_GIT);
      response.setHeader(ProtocolConstants.HEADER_LOCATION, resovleOrionURI(request, nu).toString());
      return true;
    } catch (Exception e) {
      return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          "An error occured when identifying a new Diff resource.", e));
    }
  }

  // private Object toJSON(ApplyResult applyResult) throws JSONException {
  // JSONObject result = new JSONObject();
  // if (applyResult.getApplyErrors().isEmpty() && applyResult.getFormatErrors().isEmpty()) {
  // result.put(GitConstants.KEY_RESULT, "Ok");
  // } else {
  // if (!applyResult.getFormatErrors().isEmpty())
  // result.put("FormatErrors", applyResult.getFormatErrors());
  // if (!applyResult.getApplyErrors().isEmpty())
  // result.put("ApplyErrors", applyResult.getApplyErrors());
  // }
  // return result;
  // }

  private String readPatch(ServletInputStream requestStream, String contentType) throws IOException {
    // fast forward stream past multi-part header
    int boundaryOff = contentType.indexOf("boundary="); //$NON-NLS-1$
    String boundary = contentType.substring(boundaryOff + "boundary=".length(), contentType.length()); //$NON-NLS-1$
    Map<String, String> parts = IOUtilities.parseMultiPart(requestStream, boundary);
    if ("fileRadio".equals(parts.get("radio"))) //$NON-NLS-1$ //$NON-NLS-2$
      return parts.get("uploadedfile"); //$NON-NLS-1$
    if ("urlRadio".equals(parts.get("radio"))) //$NON-NLS-1$ //$NON-NLS-2$
      return fetchPatchContentFromUrl(parts.get("url")); //$NON-NLS-1$
    return null;
  }

  private String fetchPatchContentFromUrl(final String url) throws IOException {
    GetMethod m = new GetMethod(url);
    try {
      getHttpClient().executeMethod(m);
      if (m.getStatusCode() == HttpStatus.SC_OK) {
        return IOUtilities.toString(m.getResponseBodyAsStream());
      }
    } finally {
      m.releaseConnection();
    }
    return null;
  }

  private HttpClient getHttpClient() {
    if (this.httpClient == null)
      this.httpClient = new HttpClient();
    return this.httpClient;
  }

  private AbstractTreeIterator getTreeIterator(Repository db, String name) throws IOException {
    final ObjectId id = db.resolve(name);
    if (id == null)
      throw new IllegalArgumentException(name);
    final CanonicalTreeParser p = new CanonicalTreeParser();
    final ObjectReader or = db.newObjectReader();
    try {
      p.reset(or, new RevWalk(db).parseTree(id));
      return p;
    } finally {
      or.release();
    }
  }
}
TOP

Related Classes of org.eclipse.orion.server.git.servlets.GitDiffHandlerV1

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.