diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/AbstractCommand.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/AbstractCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..bb23ee2b878e39d8a225bf7f2e56db8b25416de0 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/AbstractCommand.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.rpc.client.cli; + +/** + * Superclass for dss command-line client commands. + * + * @author Chandrasekhar Ramakrishnan + */ +abstract class AbstractCommand implements ICommand +{ + + /** + * How is the program invoked from the command line? Used for displaying help. + */ + protected String getProgramCallString() + { + return "dss " + getName(); + } + + /** + * The text "usage: " + the program call string. Used for displaying help. + */ + protected String getUsagePrefixString() + { + return "usage: " + getProgramCallString(); + } + +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/CommandFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/CommandFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..e213cdab4b1c5a99e99110915c48ab9f8452dfd6 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/CommandFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.rpc.client.cli; + +import java.io.PrintStream; + +import ch.systemsx.cisd.openbis.dss.component.IDataSetDss; + +/** + * @author Chandrasekhar Ramakrishnan + */ +class CommandFactory +{ + + ICommand tryCommandForName(String name, IDataSetDss dataSet) + { + if ("ls".equals(name)) + { + return new CommandLs(dataSet); + } + + return null; + } + + void printHelpForName(String name, PrintStream out) + { + if ("ls".equals(name)) + { + CommandLs command = new CommandLs(null); + command.printHelp(out); + } + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/CommandLs.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/CommandLs.java new file mode 100644 index 0000000000000000000000000000000000000000..31af08de28e76f3b7f956e9d03b1a132c286a37b --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/CommandLs.java @@ -0,0 +1,138 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.rpc.client.cli; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.args4j.Argument; +import ch.systemsx.cisd.args4j.CmdLineParser; +import ch.systemsx.cisd.args4j.ExampleMode; +import ch.systemsx.cisd.args4j.Option; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.openbis.dss.component.IDataSetDss; +import ch.systemsx.cisd.openbis.dss.rpc.shared.FileInfoDss; + +/** + * Comand that lists files in the data set. + * + * @author Chandrasekhar Ramakrishnan + */ +class CommandLs extends AbstractCommand +{ + private static class CommandLsArguments + { + @Option(name = "r", longName = "recursive", usage = "Recurse into directories") + private boolean recursive = false; + + @Argument + private final List<String> arguments = new ArrayList<String>(); + + public boolean isRecursive() + { + return recursive; + } + + // Accessed via reflection + @SuppressWarnings("unused") + public void setRecursive(boolean recursive) + { + this.recursive = recursive; + } + + public List<String> getArguments() + { + return arguments; + } + } + + private final CommandLsArguments arguments; + + private final CmdLineParser parser; + + private final IDataSetDss dataSet; + + /** + * Constructor for the command. The dataSet may be null if this command will only be used to + * print help. + * + * @param dataSet + */ + CommandLs(IDataSetDss dataSet) + { + arguments = new CommandLsArguments(); + parser = new CmdLineParser(arguments); + this.dataSet = dataSet; + } + + public int execute(String[] args) throws UserFailureException, EnvironmentFailureException + { + FileInfoDss[] fileInfos = getFileInfos(args); + printFileInfos(fileInfos); + + return 0; + } + + private FileInfoDss[] getFileInfos(String[] args) + { + parser.parseArgument(args); + + String path; + if (arguments.getArguments().isEmpty()) + { + path = "/"; + } else + { + path = arguments.getArguments().get(0); + } + + return dataSet.listFiles(path, arguments.isRecursive()); + } + + private void printFileInfos(FileInfoDss[] fileInfos) + { + for (FileInfoDss fileInfo : fileInfos) + { + StringBuilder sb = new StringBuilder(); + sb.append(fileInfo.getPath()); + sb.append(" -- "); + if (fileInfo.isDirectory()) + { + sb.append("Directory"); + } else + { + sb.append(fileInfo.getFileSize()); + } + System.out.println(sb.toString()); + } + } + + public String getName() + { + return "ls"; + } + + public void printHelp(PrintStream out) + { + out.println(getProgramCallString() + " [options] <path>"); + parser.printUsage(out); + out.println(" Example : " + getProgramCallString() + " " + + parser.printExample(ExampleMode.ALL)); + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/GlobalArguments.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/GlobalArguments.java new file mode 100644 index 0000000000000000000000000000000000000000..803361d96617a1e7c8d827a9ce3feac1b62c1da0 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/GlobalArguments.java @@ -0,0 +1,163 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.rpc.client.cli; + +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.args4j.Argument; +import ch.systemsx.cisd.args4j.Option; + +/** + * Command line arguments for the dss command. The format is: + * <p> + * <code> + * [options] DATA_SET_CODE COMMAND [COMMAND_ARGS] + * </code> + * + * @author Chandrasekhar Ramakrishnan + */ +class GlobalArguments +{ + @Option(name = "u", longName = "username", usage = "User login name (required)") + private String username = ""; + + @Option(name = "p", longName = "password", usage = "User login password (required)") + private String password = ""; + + @Option(name = "s", longName = "server-base-url", usage = "URL for openBIS Server (required)") + private String serverBaseUrl = ""; + + @Option(name = "h", longName = "help", skipForExample = true) + private boolean isHelp = false; + + @Argument + private List<String> arguments = new ArrayList<String>(); + + public boolean isHelp() + { + return isHelp; + } + + public void setHelp(boolean isHelp) + { + this.isHelp = isHelp; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getServerBaseUrl() + { + return serverBaseUrl; + } + + public void setServerBaseUrl(String serverBaseUrl) + { + this.serverBaseUrl = serverBaseUrl; + } + + public String getDataSetCode() + { + List<String> args = getArguments(); + if (args.size() < 1) + { + return ""; + } + return args.get(0); + } + + public String getCommand() + { + List<String> args = getArguments(); + if (args.size() < 2) + { + return ""; + } + return args.get(1); + } + + public boolean hasCommand() + { + return getArguments().size() > 1; + } + + public List<String> getCommandArguments() + { + List<String> args = getArguments(); + if (args.size() < 3) + { + return new ArrayList<String>(); + } + return args.subList(2, args.size()); + } + + private List<String> getArguments() + { + return arguments; + } + + /** + * Check that the arguments make sense. + */ + public boolean isComplete() + { + if (isHelp) + { + return true; + } + + // At the moment, username, passowrd, and server base url should all be non-empty + if (username.length() < 1) + { + return false; + } + + if (password.length() < 1) + { + return false; + } + if (serverBaseUrl.length() < 1) + { + return false; + } + if (getDataSetCode().length() < 1) + { + return false; + } + + return true; + + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/ICommand.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/ICommand.java new file mode 100644 index 0000000000000000000000000000000000000000..9cba24a76fd0f4e381541cef2eccb596a6f86465 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/ICommand.java @@ -0,0 +1,62 @@ +/* + * Copyright 2009 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.rpc.client.cli; + +import java.io.PrintStream; + +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.exceptions.UserFailureException; + +/** + * A <code>ICommand</code> encapsulates one action that gets called on the client side using the + * prompt or terminal window. + * + * @author Bernd Rinn + */ +public interface ICommand +{ + + /** + * Calls this <code>ICommand</code> with given <code>arguments</code>. + * <p> + * The arguments are the <code>main(String[])</code> method ones. + * </p> + * Note that this method is expected to throw given <code>RuntimeException</code> + * (<i>unchecked</i>) exceptions. So do not catch them and let the <i>caller</i> handle them. + * + * @return exit code, will be used in <code>System.exit()</code>. + */ + public int execute(final String[] arguments) throws UserFailureException, + EnvironmentFailureException; + + /** + * Returns the name of this command. + * <p> + * On the client side, this <code>ICommand</code> is registered with this name. This is kind of + * unique identifier of this <code>ICommand</code>. + * </p> + */ + public String getName(); + + /** + * Prints usage information for this command. + * + * @param out The stream to which help is printed + */ + public void printHelp(PrintStream out); + +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/dss.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/dss.java index 9c5bd1ef1b0dace91cd77372f9bbc6122398836f..2177e4bd4832183620d472f289d29539dce95418 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/dss.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/dss.java @@ -16,10 +16,35 @@ package ch.systemsx.cisd.openbis.dss.rpc.client.cli; +import java.io.PrintStream; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLHandshakeException; + +import org.apache.commons.lang.StringUtils; +import org.springframework.remoting.RemoteAccessException; +import org.springframework.remoting.RemoteConnectFailureException; + +import ch.systemsx.cisd.args4j.CmdLineParser; +import ch.systemsx.cisd.args4j.ExampleMode; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.exceptions.InvalidSessionException; +import ch.systemsx.cisd.common.exceptions.MasqueradingException; +import ch.systemsx.cisd.common.exceptions.SystemExitException; +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.common.utilities.IExitHandler; +import ch.systemsx.cisd.common.utilities.SystemExit; +import ch.systemsx.cisd.openbis.dss.component.IDataSetDss; import ch.systemsx.cisd.openbis.dss.component.IDssComponent; import ch.systemsx.cisd.openbis.dss.component.impl.DssComponent; /** + * The dss command which supports + * <ul> + * <li>ls — list files in a data set</li> + * <li>get — get files in a data set</li> + * </ul> + * * @author Chandrasekhar Ramakrishnan */ public class dss @@ -31,24 +56,230 @@ public class dss "org.apache.commons.logging.impl.NoOpLog"); } - private final IDssComponent component; + private final GlobalArguments arguments; + + private final CmdLineParser parser; + + private final CommandFactory commandFactory; + + private final IExitHandler exitHandler; private dss() { - this.component = new DssComponent("http://localhost:8888/openbis"); + this.exitHandler = SystemExit.SYSTEM_EXIT; + this.arguments = new GlobalArguments(); + this.parser = new CmdLineParser(arguments); + this.commandFactory = new CommandFactory(); } private void runWithArgs(String[] args) { - component.login("test", "foo"); - System.out.println("" + component.getDataSet("20100318094819344-4")); + parser.parseArgument(args); + + // Show help and exit + if (arguments.isHelp()) + { + printHelp(System.out); + exitHandler.exit(0); + } + + // Show usage and exit + if (arguments.isComplete() == false) + { + printUsage(System.err); + exitHandler.exit(1); + } + + // Login to DSS + IDssComponent component = loginOrDie(); + + int resultCode = 0; + + try + { + // Get the data set + IDataSetDss dataSet = component.getDataSet(arguments.getDataSetCode()); + + // Find the command and run it + ICommand cmd = commandFactory.tryCommandForName(arguments.getCommand(), dataSet); + String[] cmdArgs = new String[arguments.getCommandArguments().size()]; + arguments.getCommandArguments().toArray(cmdArgs); + resultCode = cmd.execute(cmdArgs); + } catch (final InvalidSessionException ex) + { + System.err + .println("Your session is no longer valid. Please login again. [server said: '" + + ex.getMessage() + "']"); + resultCode = 1; + } catch (final UserFailureException ex) + { + System.err.println(); + System.err.println(ex.getMessage()); + resultCode = 1; + } catch (final EnvironmentFailureException ex) + { + System.err.println(); + System.err.println(ex.getMessage() + " (environment failure)"); + resultCode = 1; + } catch (final RemoteConnectFailureException ex) + { + System.err.println(); + System.err.println("Remote server cannot be reached (environment failure)"); + resultCode = 1; + } catch (final RemoteAccessException ex) + { + System.err.println(); + final Throwable cause = ex.getCause(); + if (cause != null) + { + if (cause instanceof UnknownHostException) + { + System.err.println(String.format( + "Given host '%s' can not be reached (environment failure)", cause + .getMessage())); + } else if (cause instanceof IllegalArgumentException) + { + System.err.println(cause.getMessage()); + } else if (cause instanceof SSLHandshakeException) + { + final String property = "javax.net.ssl.trustStore"; + System.err.println(String.format( + "Validation of SSL certificate failed [%s=%s] (configuration failure)", + property, StringUtils.defaultString(System.getProperty(property)))); + } else + { + ex.printStackTrace(); + } + } else + { + ex.printStackTrace(); + } + resultCode = 1; + } catch (final SystemExitException e) + { + resultCode = 1; + } catch (MasqueradingException e) + { + System.err.println(e); + resultCode = 1; + } catch (IllegalArgumentException e) + { + System.err.println(e.getMessage()); + resultCode = 1; + } catch (final Exception e) + { + System.err.println(); + e.printStackTrace(); + resultCode = 1; + } finally + { + // Cleanup + component.logout(); + } + + exitHandler.exit(resultCode); + } + + /** + * Log in to openBIS or exit if login fails. + */ + private IDssComponent loginOrDie() + { + try + { + IDssComponent component = new DssComponent(arguments.getServerBaseUrl()); + component.login(arguments.getUsername(), arguments.getPassword()); + return component; + } catch (final InvalidSessionException ex) + { + System.err + .println("Your session is no longer valid. Please login again. [server said: '" + + ex.getMessage() + "']"); + exitHandler.exit(1); + } catch (final UserFailureException ex) + { + System.err.println(); + System.err.println(ex.getMessage()); + exitHandler.exit(1); + } catch (final EnvironmentFailureException ex) + { + System.err.println(); + System.err.println(ex.getMessage() + " (environment failure)"); + exitHandler.exit(1); + } catch (final RemoteConnectFailureException ex) + { + System.err.println(); + System.err.println("Remote server cannot be reached (environment failure)"); + exitHandler.exit(1); + } catch (final RemoteAccessException ex) + { + System.err.println(); + final Throwable cause = ex.getCause(); + if (cause != null) + { + if (cause instanceof UnknownHostException) + { + System.err.println(String.format( + "Given host '%s' can not be reached (environment failure)", cause + .getMessage())); + } else if (cause instanceof IllegalArgumentException) + { + System.err.println(cause.getMessage()); + } else if (cause instanceof SSLHandshakeException) + { + final String property = "javax.net.ssl.trustStore"; + System.err.println(String.format( + "Validation of SSL certificate failed [%s=%s] (configuration failure)", + property, StringUtils.defaultString(System.getProperty(property)))); + } else + { + ex.printStackTrace(); + } + } else + { + ex.printStackTrace(); + } + exitHandler.exit(1); + } catch (final SystemExitException e) + { + exitHandler.exit(1); + } catch (MasqueradingException e) + { + System.err.println(e); + exitHandler.exit(1); + } catch (final Exception e) + { + System.err.println(); + e.printStackTrace(); + exitHandler.exit(1); + } + + // never reached + return null; + } + + private void printHelp(PrintStream out) + { + if (arguments.hasCommand()) + { + commandFactory.printHelpForName(arguments.getCommand(), out); + } else + { + printUsage(out); + } + } + + private void printUsage(PrintStream out) + { + out.println("usage: dss [options...] COMMAND [ARGS]"); + parser.printUsage(out); + out.println(" Example : dss " + parser.printExample(ExampleMode.ALL)); } public static void main(String[] args) { dss newMe = new dss(); newMe.runWithArgs(args); - } } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/GlobalArgumentsTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/GlobalArgumentsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4bca23cc9244be7ad918b0eb329de7d97765b70f --- /dev/null +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/rpc/client/cli/GlobalArgumentsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.rpc.client.cli; + +import org.testng.AssertJUnit; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.args4j.CmdLineException; +import ch.systemsx.cisd.args4j.CmdLineParser; + +/** + * @author Chandrasekhar Ramakrishnan + */ +public class GlobalArgumentsTest extends AssertJUnit +{ + + private GlobalArguments arguments; + + private CmdLineParser parser; + + @BeforeMethod + public void setUp() + { + arguments = new GlobalArguments(); + parser = new CmdLineParser(arguments); + } + + @Test + public void testParseBasicArguments() + { + String[] args = + { "-u", "foo", "-p", "bar", "-s", "http://localhost:8888/openbis", + "20100318094819344-4" }; + try + { + parser.parseArgument(args); + assertEquals("foo", arguments.getUsername()); + assertEquals("bar", arguments.getPassword()); + assertEquals("http://localhost:8888/openbis", arguments.getServerBaseUrl()); + assertEquals("20100318094819344-4", arguments.getDataSetCode()); + assertTrue(arguments.isComplete()); + } catch (CmdLineException c) + { + fail("Should have parsed arguments"); + } + } + + @Test + public void testParseIncompleteArguments() + { + String[] args = + { "-u", "foo", "-p", "bar", "-s", "http://localhost:8888/openbis" }; + try + { + parser.parseArgument(args); + assertFalse(arguments.isComplete()); + } catch (CmdLineException c) + { + fail("Should have parsed arguments"); + } + } + + @Test + public void testHelp() + { + String[] args = + { "-h" }; + try + { + parser.parseArgument(args); + assertTrue(arguments.isComplete()); + } catch (CmdLineException c) + { + fail("Should have parsed arguments"); + } + } +}