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 ... ");
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 repository: " + err.getLocalizedMessage());
135 err.printStackTrace(System.err);
136 }
137 }
138 }
139 });
140
141 eventThread.start();
142 } catch (Exception err) {
143 System.out.println("Error: " + err.getLocalizedMessage());
144 err.printStackTrace(System.err);
145 }
146 }
147
148 /**
149 * Generate the main menu for the console-based application.
150 */
151 protected void displayMainMenu() {
152 selectionToSourceName.clear();
153 System.out.println("-----------------------------------");
154 System.out.println("Menu:");
155 System.out.println();
156 System.out.println("Select a repository to view:");
157 int selection = 1;
158 for (String sourceName : repositoryClient.getNamesOfRepositories()) {
159 selectionToSourceName.put(selection, sourceName);
160 System.out.println(StringUtil.justifyRight("" + selection++, 3, ' ') + ") " + sourceName);
161 }
162 System.out.println("or");
163 System.out.println(" ?) Show this menu");
164 System.out.println(" q) Quit");
165 }
166
167 /**
168 * Display the menu for navigating the source with the supplied name. This method returns as soon as the user exits the
169 * source.
170 *
171 * @param sourceName the source to be navigated; may not be null
172 */
173 protected void displayNavigationMenu( String sourceName ) {
174 assert sourceName != null;
175 String currentPath = "/";
176 System.out.println();
177 System.out.println("Entering the \"" + sourceName + "\" repository.");
178 displayNavigationHelp();
179 while (true) {
180 try {
181 // Print the prompt and read the input command ...
182 System.out.print(sourceName + "> ");
183 String input = in.readLine().trim();
184
185 // Process the command ...
186 if (input.length() == 0) continue;
187 if ("?".equals(input) || "help".equals(input) || "h".equals(input)) {
188 displayNavigationHelp();
189 } else if ("pwd".equals(input)) {
190 System.out.println(currentPath);
191 } else if ("exit".equals(input)) {
192 return;
193 } else if (input.startsWith("ls") || input.startsWith("ll")) {
194 input = input.substring(2).trim();
195 String path = repositoryClient.buildPath(currentPath, input);
196 displayNode(sourceName, path);
197 } else if (input.startsWith("cd ")) {
198 input = input.substring("cd ".length()).trim();
199 if (input.length() == 0) continue;
200 // Change the current path to the new location
201 String oldPath = currentPath;
202 currentPath = repositoryClient.buildPath(currentPath, input);
203 // If the current path does not exist, then go back to the previous path ...
204 if (!repositoryClient.getNodeInfo(sourceName, currentPath, null, null)) {
205 System.out.println("\"" + currentPath + "\" does not exist");
206 currentPath = oldPath;
207 } else {
208 System.out.println(currentPath);
209 }
210 }
211 } catch (Throwable e) {
212 displayError(" processing your command", e);
213 }
214 }
215 }
216
217 protected void displayNavigationHelp() {
218 System.out.println();
219 System.out.println("Enter one of the following commands followed by RETURN:");
220 System.out.println(" pwd print the current node's path");
221 System.out.println(" ls [path] to list the details of the node at the specified absolute or relative path");
222 System.out.println(" (or the current path if none is supplied)");
223 System.out.println(" cd path to change to the node at the specified absolute or relative path");
224 System.out.println(" exit to exit this repository and return to the main menu");
225 System.out.println();
226 }
227
228 /**
229 * Display the node with the given path found in the supplied source.
230 *
231 * @param sourceName the name of the source; may not be null
232 * @param path the path to the node; may not be null
233 */
234 protected void displayNode( String sourceName,
235 String path ) {
236 assert sourceName != null;
237 assert path != null;
238
239 // Retrieve the node information from the client ...
240 Map<String, Object[]> properties = new HashMap<String, Object[]>();
241 List<String> children = new ArrayList<String>();
242 try {
243 repositoryClient.getNodeInfo(sourceName, path, properties, children);
244 } catch (Throwable t) {
245 displayError(" displaying node \"" + path + "\"", t);
246 }
247
248 // Print the './' and '../' options ...
249 System.out.println(" ./");
250 System.out.println(" ../");
251
252 // Display the children ...
253 for (String childName : children) {
254 System.out.println(" " + childName + "/");
255 }
256 // Determine the maximum length of the properties so that we can left-justify the values
257 int maxLength = 5;
258 for (String propertyName : properties.keySet()) {
259 maxLength = Math.max(maxLength, propertyName.length());
260 }
261 // Display the properties ...
262 for (Map.Entry<String, Object[]> property : properties.entrySet()) {
263 String name = StringUtil.justifyLeft(property.getKey(), maxLength, ' ');
264 Object[] values = property.getValue();
265 String valueStr = values.length == 1 ? values[0].toString() : Arrays.asList(values).toString();
266 System.out.println(" " + name + " = " + valueStr);
267 }
268 }
269
270 /**
271 * Display the supplied error that happened during the activity.
272 *
273 * @param activity the activity; may not be null but may be empty
274 * @param t the exception; may not be null
275 */
276 public void displayError( String activity,
277 Throwable t ) {
278 assert activity != null;
279 assert t != null;
280 System.err.println();
281 System.err.println("There has been an error" + activity);
282 System.err.println(" " + t.getMessage());
283 t.printStackTrace(System.err);
284 System.err.println();
285 System.err.println("Press any key to continue:");
286 try {
287 in.readLine();
288 } catch (IOException err) {
289 err.printStackTrace(System.err);
290 }
291 }
292
293 /**
294 * {@inheritDoc}
295 *
296 * @see org.jboss.example.dna.repository.UserInterface#getLocationOfRepositoryFiles()
297 */
298 public String getLocationOfRepositoryFiles() {
299 return new File("").getAbsolutePath();
300 }
301
302 /**
303 * {@inheritDoc}
304 *
305 * @see org.jboss.example.dna.repository.UserInterface#getRepositoryConfiguration()
306 */
307 public File getRepositoryConfiguration() {
308 return new File("configRepository.xml");
309 }
310
311 /**
312 * {@inheritDoc}
313 *
314 * @see org.jboss.example.dna.repository.UserInterface#getLocationOfCndFiles()
315 */
316 public String getLocationOfCndFiles() {
317 return new File("").getAbsolutePath();
318 }
319
320 /**
321 * {@inheritDoc}
322 *
323 * @see org.jboss.example.dna.repository.UserInterface#getCallbackHandler()
324 */
325 public CallbackHandler getCallbackHandler() {
326 return new TextCallbackHandler();
327 }
328
329 }