001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.example.dna.repository;
025
026 import java.io.BufferedReader;
027 import java.io.File;
028 import java.io.IOException;
029 import java.io.InputStreamReader;
030 import java.util.ArrayList;
031 import java.util.Arrays;
032 import java.util.HashMap;
033 import java.util.List;
034 import java.util.Map;
035 import javax.security.auth.callback.CallbackHandler;
036 import org.jboss.dna.common.util.StringUtil;
037 import com.sun.security.auth.callback.TextCallbackHandler;
038
039 /**
040 * @author Randall Hauch
041 */
042 public class ConsoleInput implements UserInterface {
043
044 protected static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
045
046 private final RepositoryClient repositoryClient;
047 private final Map<Integer, String> selectionToSourceName = new HashMap<Integer, String>();
048
049 /**
050 * Construct the console input and prompt for user input to interact with the RepositoryClient.
051 *
052 * @param client the client that should be used; may not be null
053 * @param args the command-line arguments; may not be null but may be empty
054 */
055 public ConsoleInput( final RepositoryClient client,
056 final String[] args ) {
057 assert client != null;
058 this.repositoryClient = client;
059 for (String arg : args) {
060 arg = arg.trim().toLowerCase();
061 if (arg.equals("--help")) {
062 System.out.println();
063 System.out.println("Usage: run.sh [options]");
064 System.out.println();
065 System.out.println("Options:");
066 System.out.println(" --api=value Specify which API should be used to obtain the content.");
067 System.out.println(" The 'value' must be either 'jcr' or 'dna', and defaults");
068 System.out.println(" to 'jcr'.");
069 System.out.println(" --jaas Specify that JAAS should be used to authenticate the user.");
070 System.out.println(" --jaas=name With no 'name', use JAAS with an application context");
071 System.out.println(" named \"" + RepositoryClient.JAAS_LOGIN_CONTEXT_NAME + "\".");
072 System.out.println(" If another application context is to be used, then specify");
073 System.out.println(" the name.");
074 System.out.println(" --help Print these instructions and exit.");
075 System.out.println();
076 return;
077 }
078 }
079 try {
080 Thread eventThread = new Thread(new Runnable() {
081
082 private boolean quit = false;
083
084 @SuppressWarnings( "synthetic-access" )
085 public void run() {
086 try {
087 System.out.println();
088 System.out.print("Starting repositories ... ");
089 client.startRepositories();
090 System.out.println("done.");
091 System.out.println();
092 displayMainMenu();
093
094 while (!quit) {
095 System.out.print(">");
096 try {
097 String input = in.readLine().trim();
098 if ("?".equals(input) || "h".equals(input)) displayMainMenu();
099 else if ("q".equals(input)) quit = true;
100 else {
101 try {
102 int selection = Integer.parseInt(input);
103 String sourceName = selectionToSourceName.get(selection);
104 displayNavigationMenu(sourceName);
105 displayMainMenu();
106 } catch (NumberFormatException e) {
107 System.out.println("Invalid option.");
108 displayMainMenu();
109 }
110 }
111 } catch (NumberFormatException e) {
112 System.out.println("Invalid integer " + e.getMessage());
113 } catch (IllegalArgumentException e) {
114 System.out.println(e.getMessage());
115 } catch (IOException e) {
116 e.printStackTrace();
117 } catch (Throwable e) {
118 e.printStackTrace();
119 }
120 }
121 } catch (Exception err) {
122 System.out.println("Error: " + err.getLocalizedMessage());
123 err.printStackTrace(System.err);
124 } finally {
125 try {
126 // Terminate ...
127 System.out.println();
128 System.out.print("done.\nShutting down repositories and services ... ");
129 client.shutdown();
130 System.out.print("done.");
131 System.out.println();
132 System.out.println();
133 } catch (Exception err) {
134 System.out.println("Error shutting down sequencing service and repository: "
135 + err.getLocalizedMessage());
136 err.printStackTrace(System.err);
137 }
138 }
139 }
140 });
141
142 eventThread.start();
143 } catch (Exception err) {
144 System.out.println("Error: " + err.getLocalizedMessage());
145 err.printStackTrace(System.err);
146 }
147 }
148
149 /**
150 * Generate the main menu for the console-based application.
151 */
152 protected void displayMainMenu() {
153 selectionToSourceName.clear();
154 System.out.println("-----------------------------------");
155 System.out.println("Menu:");
156 System.out.println();
157 System.out.println("Select a repository to view:");
158 int selection = 1;
159 for (String sourceName : repositoryClient.getNamesOfRepositories()) {
160 selectionToSourceName.put(selection, sourceName);
161 System.out.println(StringUtil.justifyRight("" + selection++, 3, ' ') + ") " + sourceName);
162 }
163 System.out.println("or");
164 System.out.println(" ?) Show this menu");
165 System.out.println(" q) Quit");
166 }
167
168 /**
169 * Display the menu for navigating the source with the supplied name. This method returns as soon as the user exits the
170 * source.
171 *
172 * @param sourceName the source to be navigated; may not be null
173 */
174 protected void displayNavigationMenu( String sourceName ) {
175 assert sourceName != null;
176 String currentPath = "/";
177 System.out.println();
178 System.out.println("Entering the \"" + sourceName + "\" repository.");
179 displayNavigationHelp();
180 while (true) {
181 try {
182 // Print the prompt and read the input command ...
183 System.out.print(sourceName + "> ");
184 String input = in.readLine().trim();
185
186 // Process the command ...
187 if (input.length() == 0) continue;
188 if ("?".equals(input) || "help".equals(input) || "h".equals(input)) {
189 displayNavigationHelp();
190 } else if ("pwd".equals(input)) {
191 System.out.println(currentPath);
192 } else if ("exit".equals(input)) {
193 return;
194 } else if (input.startsWith("ls") || input.startsWith("ll")) {
195 input = input.substring(2).trim();
196 String path = repositoryClient.buildPath(currentPath, input);
197 displayNode(sourceName, path);
198 } else if (input.startsWith("cd ")) {
199 input = input.substring("cd ".length()).trim();
200 if (input.length() == 0) continue;
201 // Change the current path to the new location
202 String oldPath = currentPath;
203 currentPath = repositoryClient.buildPath(currentPath, input);
204 // If the current path does not exist, then go back to the previous path ...
205 if (!repositoryClient.getNodeInfo(sourceName, currentPath, null, null)) {
206 System.out.println("\"" + currentPath + "\" does not exist");
207 currentPath = oldPath;
208 } else {
209 System.out.println(currentPath);
210 }
211 }
212 } catch (Throwable e) {
213 displayError(" processing your command", e);
214 }
215 }
216 }
217
218 protected void displayNavigationHelp() {
219 System.out.println();
220 System.out.println("Enter one of the following commands followed by RETURN:");
221 System.out.println(" pwd print the current node's path");
222 System.out.println(" ls [path] to list the details of the node at the specified absolute or relative path");
223 System.out.println(" (or the current path if none is supplied)");
224 System.out.println(" cd path to change to the node at the specified absolute or relative path");
225 System.out.println(" exit to exit this repository and return to the main menu");
226 System.out.println();
227 }
228
229 /**
230 * Display the node with the given path found in the supplied source.
231 *
232 * @param sourceName the name of the source; may not be null
233 * @param path the path to the node; may not be null
234 */
235 protected void displayNode( String sourceName,
236 String path ) {
237 assert sourceName != null;
238 assert path != null;
239
240 // Retrieve the node information from the client ...
241 Map<String, Object[]> properties = new HashMap<String, Object[]>();
242 List<String> children = new ArrayList<String>();
243 try {
244 repositoryClient.getNodeInfo(sourceName, path, properties, children);
245 } catch (Throwable t) {
246 displayError(" displaying node \"" + path + "\"", t);
247 }
248
249 // Print the './' and '../' options ...
250 System.out.println(" ./");
251 System.out.println(" ../");
252
253 // Display the children ...
254 for (String childName : children) {
255 System.out.println(" " + childName + "/");
256 }
257 // Determine the maximum length of the properties so that we can left-justify the values
258 int maxLength = 5;
259 for (String propertyName : properties.keySet()) {
260 maxLength = Math.max(maxLength, propertyName.length());
261 }
262 // Display the properties ...
263 for (Map.Entry<String, Object[]> property : properties.entrySet()) {
264 String name = StringUtil.justifyLeft(property.getKey(), maxLength, ' ');
265 Object[] values = property.getValue();
266 String valueStr = values.length == 1 ? values[0].toString() : Arrays.asList(values).toString();
267 System.out.println(" " + name + " = " + valueStr);
268 }
269 }
270
271 /**
272 * Display the supplied error that happened during the activity.
273 *
274 * @param activity the activity; may not be null but may be empty
275 * @param t the exception; may not be null
276 */
277 protected void displayError( String activity,
278 Throwable t ) {
279 assert activity != null;
280 assert t != null;
281 System.err.println();
282 System.err.println("There has been an error" + activity);
283 System.err.println(" " + t.getMessage());
284 t.printStackTrace(System.err);
285 System.err.println();
286 System.err.println("Press any key to continue:");
287 try {
288 in.readLine();
289 } catch (IOException err) {
290 err.printStackTrace(System.err);
291 }
292 }
293
294 /**
295 * {@inheritDoc}
296 *
297 * @see org.jboss.example.dna.repository.UserInterface#getLocationOfRepositoryFiles()
298 */
299 public String getLocationOfRepositoryFiles() {
300 return new File("").getAbsolutePath();
301 }
302
303 /**
304 * {@inheritDoc}
305 *
306 * @see org.jboss.example.dna.repository.UserInterface#getCallbackHandler()
307 */
308 public CallbackHandler getCallbackHandler() {
309 return new TextCallbackHandler();
310 }
311
312 }