diff --git a/core/src/main/java/org/jclouds/http/binders/JsonBinder.java b/core/src/main/java/org/jclouds/http/binders/JsonBinder.java index 2c564c5639..cce3f3199a 100644 --- a/core/src/main/java/org/jclouds/http/binders/JsonBinder.java +++ b/core/src/main/java/org/jclouds/http/binders/JsonBinder.java @@ -24,6 +24,8 @@ package org.jclouds.http.binders; import java.util.Collections; +import static com.google.common.base.Preconditions.checkState; + import java.util.Map; import javax.ws.rs.core.HttpHeaders; @@ -38,22 +40,20 @@ import com.google.inject.Inject; /** * Binds the object to the request as a json object. * - * @author adriancole + * @author Adrian Cole * @since 4.0 */ public class JsonBinder implements PostEntityBinder { - protected final Gson gson; @Inject - public JsonBinder(Gson gson) { - this.gson = gson; - } + protected Gson gson; public void addEntityToRequest(Map postParams, HttpRequest request) { addEntityToRequest((Object) postParams, request); } public void addEntityToRequest(Object toBind, HttpRequest request) { + checkState(gson != null, "Program error: gson should have been injected at this point"); String json = gson.toJson(toBind); request.setEntity(json); request.getHeaders().replaceValues(HttpHeaders.CONTENT_LENGTH, diff --git a/core/src/main/java/org/jclouds/http/functions/config/ParserModule.java b/core/src/main/java/org/jclouds/http/functions/config/ParserModule.java index 63b4ccf83b..13c6e25882 100755 --- a/core/src/main/java/org/jclouds/http/functions/config/ParserModule.java +++ b/core/src/main/java/org/jclouds/http/functions/config/ParserModule.java @@ -60,7 +60,13 @@ import com.google.inject.assistedinject.FactoryProvider; public class ParserModule extends AbstractModule { private final static TypeLiteral parseSaxFactoryLiteral = new TypeLiteral() { }; - + + protected void configure() { + bind(parseSaxFactoryLiteral).toProvider( + FactoryProvider.newFactory(parseSaxFactoryLiteral, new TypeLiteral>() { + })); + } + static class DateTimeAdapter implements JsonSerializer, JsonDeserializer { private final static DateService dateService = new DateService(); @@ -122,9 +128,4 @@ public class ParserModule extends AbstractModule { return gson.create(); } - protected void configure() { - bind(parseSaxFactoryLiteral).toProvider( - FactoryProvider.newFactory(parseSaxFactoryLiteral, new TypeLiteral>() { - })); - } } diff --git a/core/src/main/java/org/jclouds/rest/JaxrsAnnotationProcessor.java b/core/src/main/java/org/jclouds/rest/JaxrsAnnotationProcessor.java index 8956937caa..482f30b8c8 100644 --- a/core/src/main/java/org/jclouds/rest/JaxrsAnnotationProcessor.java +++ b/core/src/main/java/org/jclouds/rest/JaxrsAnnotationProcessor.java @@ -314,8 +314,11 @@ public class JaxrsAnnotationProcessor { public PostEntityBinder getPostEntityBinderOrNull(Method method, Object[] args) { for (Object arg : args) { - if (arg instanceof PostEntityBinder) - return (PostEntityBinder) arg; + if (arg instanceof PostEntityBinder) { + PostEntityBinder binder = (PostEntityBinder) arg; + injector.injectMembers(binder); + return binder; + } } PostBinder annotation = method.getAnnotation(PostBinder.class); if (annotation != null) { diff --git a/core/src/main/java/org/jclouds/ssh/ConfiguresSshConnection.java b/core/src/main/java/org/jclouds/ssh/ConfiguresSshConnection.java new file mode 100755 index 0000000000..b6f6e4afa7 --- /dev/null +++ b/core/src/main/java/org/jclouds/ssh/ConfiguresSshConnection.java @@ -0,0 +1,42 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jclouds.ssh; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * designates the module configures an Ssh Connection. + * + * @author Adrian Cole + * + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface ConfiguresSshConnection { + +} diff --git a/core/src/main/java/org/jclouds/ssh/SshConnection.java b/core/src/main/java/org/jclouds/ssh/SshConnection.java new file mode 100644 index 0000000000..24317c85ae --- /dev/null +++ b/core/src/main/java/org/jclouds/ssh/SshConnection.java @@ -0,0 +1,29 @@ +package org.jclouds.ssh; + +import java.io.InputStream; +import java.net.InetAddress; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import com.google.inject.assistedinject.Assisted; + +/** + * @author Adrian Cole + */ +public interface SshConnection { + + public interface Factory { + SshConnection create(InetAddress host, int port, @Assisted("username") String username, + @Assisted("password") String password); + } + + InputStream get(String path); + + @PostConstruct + void connect(); + + @PreDestroy + void disconnect(); + +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/ssh/SshException.java b/core/src/main/java/org/jclouds/ssh/SshException.java new file mode 100644 index 0000000000..7972d10b4b --- /dev/null +++ b/core/src/main/java/org/jclouds/ssh/SshException.java @@ -0,0 +1,27 @@ +package org.jclouds.ssh; + +/** + * @author Adrian Cole + */ +public class SshException extends RuntimeException { + + /** The serialVersionUID */ + private static final long serialVersionUID = 7271048517353750433L; + + public SshException() { + super(); + } + + public SshException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public SshException(String arg0) { + super(arg0); + } + + public SshException(Throwable arg0) { + super(arg0); + } + +} \ No newline at end of file diff --git a/extensions/gae/src/test/java/org/jclouds/gae/config/GaeHttpCommandExecutorServiceModuleTest.java b/extensions/gae/src/test/java/org/jclouds/gae/config/GaeHttpCommandExecutorServiceModuleTest.java index 06a86be5bb..463a90145d 100644 --- a/extensions/gae/src/test/java/org/jclouds/gae/config/GaeHttpCommandExecutorServiceModuleTest.java +++ b/extensions/gae/src/test/java/org/jclouds/gae/config/GaeHttpCommandExecutorServiceModuleTest.java @@ -40,7 +40,7 @@ import com.google.inject.Injector; import com.google.inject.name.Names; /** - * // TODO: Adrian: Document this! + * Tests the ability to configure a {@link GaeHttpCommandExecutorService} * * @author Adrian Cole */ diff --git a/extensions/pom.xml b/extensions/pom.xml index fd1159295d..c3f5953210 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -40,6 +40,7 @@ gae httpnio log4j + ssh diff --git a/extensions/ssh/jsch/pom.xml b/extensions/ssh/jsch/pom.xml new file mode 100755 index 0000000000..ce65c0b1c3 --- /dev/null +++ b/extensions/ssh/jsch/pom.xml @@ -0,0 +1,48 @@ + + + + + jclouds-ssh-project + org.jclouds + 1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + jclouds-jsch + jclouds jsch ssh client + jar + jclouds jsch ssh client + + + com.jcraft + jsch + 0.1.41 + test + + + diff --git a/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java new file mode 100644 index 0000000000..5ab79d46df --- /dev/null +++ b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java @@ -0,0 +1,109 @@ +package org.jclouds.ssh.jsch; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.io.InputStream; +import java.net.InetAddress; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; + +import org.jclouds.logging.Logger; +import org.jclouds.ssh.SshConnection; +import org.jclouds.ssh.SshException; + +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; + +/** + * + * @author Adrian Cole + */ +public class JschSshConnection implements SshConnection { + + private final InetAddress host; + private final int port; + private final String username; + private final String password; + private ChannelSftp sftp; + @Resource + protected Logger logger = Logger.NULL; + + @Inject + public JschSshConnection(@Assisted InetAddress host, @Assisted int port, + @Assisted("username") String username, @Assisted("password") String password) { + this.host = checkNotNull(host, "host"); + checkArgument(port > 0, "ssh port must be greater then zero" + port); + this.port = port; + this.username = checkNotNull(username, "username"); + this.password = checkNotNull(password, "password"); + } + + public InputStream get(String path) { + checkConnected(); + checkNotNull(path, "path"); + try { + return sftp.get(path); + } catch (SftpException e) { + throw new SshException(String.format("%s@%s:%d: Error getting path: %s", username, host + .getHostAddress(), port, path), e); + } + } + + private void checkConnected() { + checkState(sftp.isConnected(), String.format("%s@%s:%d: SFTP not connected!", username, host + .getHostAddress(), port)); + } + + @PostConstruct + public void connect() { + if (sftp != null && sftp.isConnected()) + return; + JSch jsch = new JSch(); + Session session = null; + Channel channel = null; + try { + session = jsch.getSession(username, host.getHostAddress(), port); + } catch (JSchException e) { + throw new SshException(String.format("%s@%s:%d: Error creating session.", username, host + .getHostAddress(), port), e); + } + logger.debug("%s@%s:%d: Session created.", username, host.getHostAddress(), port); + session.setPassword(password); + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config); + try { + session.connect(); + } catch (JSchException e) { + throw new SshException(String.format("%s@%s:%d: Error connecting to session.", username, + host.getHostAddress(), port), e); + } + logger.debug("%s@%s:%d: Session connected.", username, host.getHostAddress(), port); + logger.debug("%s@%s:%d: Opening sftp Channel.", username, host.getHostAddress(), port); + try { + channel = session.openChannel("sftp"); + channel.connect(); + } catch (JSchException e) { + throw new SshException(String.format("%s@%s:%d: Error connecting to sftp.", username, host + .getHostAddress(), port), e); + } + sftp = (ChannelSftp) channel; + } + + @PreDestroy + public void disconnect() { + if (sftp != null && sftp.isConnected()) + sftp.quit(); + } + +} diff --git a/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshConnectionModule.java b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshConnectionModule.java new file mode 100755 index 0000000000..c5f4e3ca3f --- /dev/null +++ b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshConnectionModule.java @@ -0,0 +1,44 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jclouds.ssh.jsch.config; + +import org.jclouds.ssh.ConfiguresSshConnection; +import org.jclouds.ssh.SshConnection; +import org.jclouds.ssh.jsch.JschSshConnection; + +import com.google.inject.AbstractModule; +import com.google.inject.assistedinject.FactoryProvider; + +/** + * + * @author Adrian Cole + */ +@ConfiguresSshConnection +public class JschSshConnectionModule extends AbstractModule { + + protected void configure() { + bind(SshConnection.Factory.class).toProvider( + FactoryProvider.newFactory(SshConnection.Factory.class, JschSshConnection.class)); + } +} \ No newline at end of file diff --git a/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java new file mode 100644 index 0000000000..02c2ef0dce --- /dev/null +++ b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java @@ -0,0 +1,72 @@ +package org.jclouds.ssh.jsch; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.apache.commons.io.IOUtils; +import org.jclouds.ssh.SshConnection; +import org.jclouds.ssh.jsch.config.JschSshConnectionModule; +import org.jclouds.util.Utils; +import org.testng.ITestContext; +import org.testng.annotations.BeforeGroups; +import org.testng.annotations.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests the ability of a {@link JschSshConnection} + * + * @author Adrian Cole + */ +@Test(groups = "live", testName = "ssh.JschSshConnectionLiveTest") +public class JschSshConnectionLiveTest { + protected static final String sshHost = System.getProperty("jclouds.test.ssh.host"); + protected static final String sshPort = System.getProperty("jclouds.test.ssh.port"); + protected static final String sshUser = System.getProperty("jclouds.test.ssh.username"); + protected static final String sshPass = System.getProperty("jclouds.test.ssh.password"); + + protected SshConnection connection; + + @BeforeGroups(groups = { "live" }) + public void setupConnection(ITestContext context) throws NumberFormatException, + UnknownHostException { + int port = (sshPort != null) ? Integer.parseInt(sshPort) : 22; + InetAddress host = (sshHost != null) ? InetAddress.getByName(sshHost) : InetAddress + .getLocalHost(); + if (sshUser == null || sshPass == null || sshUser.trim().equals("") + || sshPass.trim().equals("")) { + System.err.println("ssh credentials not present. Tests will be lame"); + connection = new SshConnection() { + + public void connect() { + } + + public void disconnect() { + } + + public InputStream get(String path) { + if (path.equals("/etc/passwd")) { + return IOUtils.toInputStream("root"); + } + throw new RuntimeException("path " + path + " not stubbed"); + } + + }; + } else { + Injector i = Guice.createInjector(new JschSshConnectionModule()); + SshConnection.Factory factory = i.getInstance(SshConnection.Factory.class); + connection = factory.create(host, port, sshUser, sshPass); + connection.connect(); + } + } + + public void testGetEtcPassword() throws IOException { + InputStream input = connection.get("/etc/passwd"); + String contents = Utils.toStringAndClose(input); + assert contents.indexOf("root") >= 0 : "no root in " + contents; + } + +} \ No newline at end of file diff --git a/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java new file mode 100644 index 0000000000..452125064d --- /dev/null +++ b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java @@ -0,0 +1,30 @@ +package org.jclouds.ssh.jsch.config; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.jclouds.ssh.SshConnection; +import org.jclouds.ssh.jsch.JschSshConnection; +import org.jclouds.ssh.jsch.config.JschSshConnectionModule; +import org.testng.annotations.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests the ability to configure a {@link JschSshConnection} + * + * @author Adrian Cole + */ +@Test +public class JschSshConnectionModuleTest { + + public void testConfigureBindsClient() throws UnknownHostException { + + Injector i = Guice.createInjector(new JschSshConnectionModule()); + SshConnection.Factory factory = i.getInstance(SshConnection.Factory.class); + SshConnection connection = factory.create(InetAddress.getLocalHost(), 22, "username", + "password"); + assert connection instanceof JschSshConnection; + } +} \ No newline at end of file diff --git a/extensions/ssh/pom.xml b/extensions/ssh/pom.xml new file mode 100755 index 0000000000..42b0b07600 --- /dev/null +++ b/extensions/ssh/pom.xml @@ -0,0 +1,106 @@ + + + + + jclouds-extensions-project + org.jclouds + 1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + jclouds-ssh-project + pom + jclouds ssh project + + jsch + + + localhost + 22 + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + + none + + + **/*IntegrationTest.java + **/*LiveTest.java + + + + file.encoding + UTF-8 + + + jclouds.test.ssh.host + ${jclouds.test.ssh.host} + + + jclouds.test.ssh.port + ${jclouds.test.ssh.port} + + + jclouds.test.ssh.username + ${jclouds.test.ssh.username} + + + jclouds.test.ssh.password + ${jclouds.test.ssh.password} + + + + + + + + + + + +