diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index 80bc6a187329..85242d0449cd 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -149,8 +149,8 @@ serviceInterface="git4idea.history.wholeTree.GitCommitsSequentially"/> - + handlers = new THashMap(); - - @NotNull - public static GitSSHService getInstance() { - return ServiceManager.getService(GitSSHService.class); - } - - /** - * @return the port number for XML RCP - */ - public int getXmlRcpPort() { - return WebServerManager.getInstance().waitForStart().getPort(); - } - - /** - * Get file to the script service - * - * @return path to the script - * @throws IOException if script cannot be generated - */ - @NotNull - public synchronized File getScriptPath() throws IOException { - if (myScriptPath == null || !myScriptPath.exists()) { - ScriptGenerator generator = new ScriptGenerator(GitSSHHandler.GIT_SSH_PREFIX, SSHMain.class, getTempDir()); - generator.addClasses(XmlRpcClientLite.class, DecoderException.class, KnownHosts.class, FileUtilRt.class); - generator.addResource(SSHMainBundle.class, "/org/jetbrains/git4idea/ssh/SSHMainBundle.properties"); - myScriptPath = generator.generate(); - } - return myScriptPath; - } - - /** - * @return the temporary directory to use or null if the default directory might be used - */ - @SuppressWarnings({"MethodMayBeStatic"}) - @Nullable - protected File getTempDir() { - return null; - } - - /** - * Register handler. Note that handlers must be unregistered using {@link #unregisterHandler(int)}. - * - * @param handler a handler to register - * @return an identifier to pass to the environment variable - */ - public synchronized int registerHandler(@NotNull GitSSHGUIHandler handler) { - XmlRpcServer xmlRpcServer = XmlRpcServer.SERVICE.getInstance(); - if (!xmlRpcServer.hasHandler(GitSSHHandler.HANDLER_NAME)) { - xmlRpcServer.addHandler(GitSSHHandler.HANDLER_NAME, new InternalRequestHandler()); - } - - while (true) { - int candidate = RANDOM.nextInt(); - if (candidate == Integer.MIN_VALUE) { - continue; - } - candidate = Math.abs(candidate); - if (handlers.containsKey(candidate)) { - continue; - } - handlers.put(candidate, handler); - return candidate; - } - } - - /** - * Get handler for the key - * - * @param key the key to use - * @return the registered handler - */ - @NotNull - private synchronized GitSSHGUIHandler getHandler(int key) { - GitSSHGUIHandler rc = handlers.get(key); - if (rc == null) { - throw new IllegalStateException("No handler for the key " + key); - } - return rc; - } - - /** - * Unregister handler by the key - * - * @param key the key to unregister - */ - public synchronized void unregisterHandler(int key) { - if (handlers.remove(key) == null) { - throw new IllegalArgumentException("The handler " + key + " is not registered"); - } - } - - /** - * Internal handler implementation class, do not use it. - */ - public class InternalRequestHandler implements GitSSHHandler { - /** - * {@inheritDoc} - */ - public boolean verifyServerHostKey(final int handler, - final String hostname, - final int port, - final String serverHostKeyAlgorithm, - final String serverHostKey, - final boolean isNew) { - return getHandler(handler).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew); - } - - /** - * {@inheritDoc} - */ - public String askPassphrase(final int handler, - final String username, - final String keyPath, - final boolean resetPassword, - final String lastError) { - return adjustNull(getHandler(handler).askPassphrase(username, keyPath, resetPassword, lastError)); - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings({"UseOfObsoleteCollectionType"}) - public Vector replyToChallenge(final int handlerNo, - final String username, - final String name, - final String instruction, - final int numPrompts, - final Vector prompt, - final Vector echo, - final String lastError) { - return adjustNull(getHandler(handlerNo).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError)); - } - - /** - * {@inheritDoc} - */ - public String askPassword(final int handlerNo, final String username, final boolean resetPassword, final String lastError) { - return adjustNull(getHandler(handlerNo).askPassword(username, resetPassword, lastError)); - } - - /** - * {@inheritDoc} - */ - @Override - public String setLastSuccessful(int handlerNo, String userName, String method, String error) { - getHandler(handlerNo).setLastSuccessful(userName, method, error); - return ""; - } - - /** - * {@inheritDoc} - */ - @Override - public String getLastSuccessful(int handlerNo, String userName) { - return getHandler(handlerNo).getLastSuccessful(userName); - } - - /** - * Adjust null value ({@code "-"} if null, {@code "+"+s) if non-null) - * - * @param s a value to adjust - * @return adjusted string - */ - private String adjustNull(final String s) { - return s == null ? "-" : "+" + s; - } - - /** - * Adjust null value (returns empty array) - * - * @param s if null return empty array - * @return s if not null, empty array otherwise - */ - @SuppressWarnings({"UseOfObsoleteCollectionType"}) - private Vector adjustNull(final Vector s) { - return s == null ? new Vector() : s; - } - } -} diff --git a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java new file mode 100644 index 000000000000..f762f1114bb6 --- /dev/null +++ b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java @@ -0,0 +1,166 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * 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 org.jetbrains.git4idea.ssh; + +import com.intellij.ide.XmlRpcServer; +import com.intellij.openapi.util.io.FileUtilRt; +import gnu.trove.THashMap; +import org.apache.commons.codec.DecoderException; +import org.apache.xmlrpc.XmlRpcClientLite; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.git4idea.util.ScriptGenerator; +import org.jetbrains.ide.WebServerManager; + +import java.io.File; +import java.io.IOException; +import java.util.Random; + +/** + *

The provider of external application scripts called by Git when a remote operation needs communication with the user.

+ *

+ * Usage: + *

    + *
  1. Get the script from {@link #getScriptPath()}.
  2. + *
  3. Set up proper environment variable + * (e.g. {@code GIT_SSH} for SSH connections, or {@code GIT_ASKPASS} for HTTP) pointing to the script.
  4. + *
  5. {@link #registerHandler(Object) Register} the handler of Git requests.
  6. + *
  7. Call Git operation.
  8. + *
  9. If the operation requires user interaction, the registered handler is called via XML RPC protocol. + * It can show a dialog in the GUI and return the answer via XML RPC to the external application, that further provides + * this value to the Git process.
  10. + *
  11. {@link #unregisterHandler(int) Unregister} the handler after operation has completed.
  12. + *
+ *

+ */ +public abstract class GitXmlRpcHandlerService { + + private static final Random RANDOM = new Random(); + + @Nullable private File myScriptPath; + @NotNull private final THashMap handlers = new THashMap(); + + /** + * @return the port number for XML RCP + */ + public int getXmlRcpPort() { + return WebServerManager.getInstance().waitForStart().getPort(); + } + + /** + * Get file to the script service + * + * @return path to the script + * @throws IOException if script cannot be generated + */ + @NotNull + public synchronized File getScriptPath() throws IOException { + if (myScriptPath == null || !myScriptPath.exists()) { + ScriptGenerator generator = new ScriptGenerator(getScriptTempFilePrefix(), getScriptMainClass(), getTempDir()); + generator.addClasses(XmlRpcClientLite.class, DecoderException.class, FileUtilRt.class); + customizeScriptGenerator(generator); + myScriptPath = generator.generate(); + } + return myScriptPath; + } + + @NotNull + protected abstract String getScriptTempFilePrefix(); + + @NotNull + protected abstract Class getScriptMainClass(); + + /** + * Adds more classes or resources to the script if needed. + */ + protected abstract void customizeScriptGenerator(@NotNull ScriptGenerator generator); + + /** + * @return the temporary directory to use or null if the default directory might be used + */ + @SuppressWarnings({"MethodMayBeStatic"}) + @Nullable + protected File getTempDir() { + return null; + } + + /** + * Register handler. Note that handlers must be unregistered using {@link #unregisterHandler(int)}. + * + * @param handler a handler to register + * @return an identifier to pass to the environment variable + */ + public synchronized int registerHandler(@NotNull T handler) { + XmlRpcServer xmlRpcServer = XmlRpcServer.SERVICE.getInstance(); + if (!xmlRpcServer.hasHandler(getRpcHandlerName())) { + xmlRpcServer.addHandler(getRpcHandlerName(), createRpcRequestHandlerDelegate()); + } + + while (true) { + int candidate = RANDOM.nextInt(); + if (candidate == Integer.MIN_VALUE) { + continue; + } + candidate = Math.abs(candidate); + if (handlers.containsKey(candidate)) { + continue; + } + handlers.put(candidate, handler); + return candidate; + } + } + + /** + * Returns the name of the handler to be used by XML RPC client to call remote methods of a proper object. + */ + @NotNull + protected abstract String getRpcHandlerName(); + + /** + * Creates an implementation of the xml rpc handler, which methods will be called from the external application. + * This method should just delegate the call to the specific handler of type {@link T}, which can be achieved by {@link #getHandler(int)}. + * @return New instance of the xml rpc handler delegate. + */ + @NotNull + protected abstract Object createRpcRequestHandlerDelegate(); + + /** + * Get handler for the key + * + * @param key the key to use + * @return the registered handler + */ + @NotNull + protected synchronized T getHandler(int key) { + T rc = handlers.get(key); + if (rc == null) { + throw new IllegalStateException("No handler for the key " + key); + } + return rc; + } + + /** + * Unregister handler by the key + * + * @param key the key to unregister + */ + public synchronized void unregisterHandler(int key) { + if (handlers.remove(key) == null) { + throw new IllegalArgumentException("The handler " + key + " is not registered"); + } + } + +} diff --git a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java new file mode 100644 index 000000000000..3b0d89e44976 --- /dev/null +++ b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java @@ -0,0 +1,120 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * 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 org.jetbrains.git4idea.ssh; + +import com.trilead.ssh2.KnownHosts; +import git4idea.commands.GitSSHGUIHandler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.git4idea.util.ScriptGenerator; + +import java.util.Vector; + +/** + * @author Kirill Likhodedov + */ +public class GitXmlRpcSshService extends GitXmlRpcHandlerService { + + @NotNull + @Override + protected String getScriptTempFilePrefix() { + return GitSSHHandler.GIT_SSH_PREFIX; + } + + @Override + protected void customizeScriptGenerator(@NotNull ScriptGenerator generator) { + generator.addClasses(KnownHosts.class); + generator.addResource(SSHMainBundle.class, "/org/jetbrains/git4idea/ssh/SSHMainBundle.properties"); + } + + @NotNull + @Override + protected Class getScriptMainClass() { + return SSHMain.class; + } + + @NotNull + @Override + protected String getRpcHandlerName() { + return GitSSHHandler.HANDLER_NAME; + } + + @NotNull + @Override + protected Object createRpcRequestHandlerDelegate() { + return new InternalRequestHandler(); + } + + /** + * Internal handler implementation class, do not use it. + */ + public class InternalRequestHandler implements GitSSHHandler { + + @Override + public boolean verifyServerHostKey(int handler, String hostname, int port, String serverHostKeyAlgorithm, String serverHostKey, + boolean isNew) { + return getHandler(handler).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew); + } + + @Override + public String askPassphrase(int handler, String username, String keyPath, boolean resetPassword, String lastError) { + return adjustNull(getHandler(handler).askPassphrase(username, keyPath, resetPassword, lastError)); + } + + @Override + @SuppressWarnings({"UseOfObsoleteCollectionType"}) + public Vector replyToChallenge(int handlerNo, String username, String name, String instruction, int numPrompts, + Vector prompt, Vector echo, String lastError) { + return adjustNull(getHandler(handlerNo).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError)); + } + + @Override + public String askPassword(int handlerNo, String username, boolean resetPassword, String lastError) { + return adjustNull(getHandler(handlerNo).askPassword(username, resetPassword, lastError)); + } + + @Override + public String setLastSuccessful(int handlerNo, String userName, String method, String error) { + getHandler(handlerNo).setLastSuccessful(userName, method, error); + return ""; + } + + @Override + public String getLastSuccessful(int handlerNo, String userName) { + return getHandler(handlerNo).getLastSuccessful(userName); + } + + /** + * Adjust null value ({@code "-"} if null, {@code "+"+s) if non-null) + * + * @param s a value to adjust + * @return adjusted string + */ + private String adjustNull(final String s) { + return s == null ? "-" : "+" + s; + } + + /** + * Adjust null value (returns empty array) + * + * @param s if null return empty array + * @return s if not null, empty array otherwise + */ + @SuppressWarnings({"UseOfObsoleteCollectionType"}) + private Vector adjustNull(final Vector s) { + return s == null ? new Vector() : s; + } + } +}