/*
* JBoss, Home of Professional Open Source.
* Copyright 2016, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.cli.handlers;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.jboss.as.cli.Attachments;
import org.jboss.as.cli.CommandContext;
import org.jboss.as.cli.CommandFormatException;
import org.jboss.as.cli.CommandLineCompleter;
import org.jboss.as.cli.CommandLineException;
import org.jboss.as.cli.Util;
import org.jboss.as.cli.impl.ArgumentWithValue;
import org.jboss.as.cli.impl.ArgumentWithoutValue;
import org.jboss.as.cli.impl.FileSystemPathArgument;
import org.jboss.as.controller.client.OperationResponse;
import org.jboss.dmr.ModelNode;
/**
*
* @author jdenise@redhat.com
*/
public class AttachmentHandler extends BatchModeCommandHandler {
private static final String DISPLAY = "display";
private static final String SAVE = "save";
private final FileSystemPathArgument targetFile;
private final ArgumentWithValue operation;
private final ArgumentWithValue action;
private final ArgumentWithoutValue overwrite;
public AttachmentHandler(CommandContext ctx) {
super(ctx, "attachment", true);
action = new ArgumentWithValue(this, new CommandLineCompleter() {
@Override
public int complete(CommandContext ctx, String buffer, int cursor,
List<String> candidates) {
if (buffer == null || buffer.isEmpty()) {
candidates.add(DISPLAY);
candidates.add(SAVE);
return cursor;
}
if (buffer.equals(DISPLAY) || buffer.equals(SAVE)) {
candidates.add(" ");
return cursor;
}
if (DISPLAY.startsWith(buffer)) {
candidates.add(DISPLAY + " ");
return 0;
}
if (SAVE.startsWith(buffer)) {
candidates.add(SAVE + " ");
return 0;
}
return -1;
}
}, 0, "--action");
operation = new ArgumentWithValue(this, new CommandLineCompleter() {
@Override
public int complete(CommandContext ctx, String buffer, int cursor,
List<String> candidates) {
final String originalLine = ctx.getParsedCommandLine().getOriginalLine();
boolean skipWS;
int wordCount;
if (Character.isWhitespace(originalLine.charAt(0))) {
skipWS = true;
wordCount = 0;
} else {
skipWS = false;
wordCount = 1;
}
int cmdStart = 1;
while (cmdStart < originalLine.length()) {
if (skipWS) {
if (!Character.isWhitespace(originalLine.charAt(cmdStart))) {
skipWS = false;
++wordCount;
if (wordCount == 3) {
break;
}
}
} else if (Character.isWhitespace(originalLine.charAt(cmdStart))) {
skipWS = true;
}
++cmdStart;
}
String cmd;
if (wordCount == 1) {
cmd = "";
} else if (wordCount != 3) {
return -1;
} else {
cmd = originalLine.substring(cmdStart);
// remove --operation=
int i = cmd.indexOf("=");
if (i > 0) {
if (i == cmd.length() - 1) {
cmd = "";
} else {
cmd = cmd.substring(i + 1);
}
}
}
int cmdResult = ctx.getDefaultCommandCompleter().complete(ctx,
cmd, cmd.length(), candidates);
if (cmdResult < 0) {
return cmdResult;
}
// escaping index correction
int escapeCorrection = 0;
int start = originalLine.length() - 1 - buffer.length();
while (start - escapeCorrection >= 0) {
final char ch = originalLine.charAt(start - escapeCorrection);
if (Character.isWhitespace(ch) || ch == '=') {
break;
}
++escapeCorrection;
}
return buffer.length() + escapeCorrection - (cmd.length() - cmdResult);
}
}, "--operation") {
@Override
public boolean canAppearNext(CommandContext ctx) throws CommandFormatException {
String act = getAction(ctx);
if (!(SAVE.equals(act) || DISPLAY.equals(act))) {
return false;
}
return super.canAppearNext(ctx);
}
};
operation.addRequiredPreceding(action);
final FilenameTabCompleter pathCompleter = FilenameTabCompleter.newCompleter(ctx);
targetFile = new FileSystemPathArgument(this, pathCompleter, "--file") {
@Override
public boolean canAppearNext(CommandContext ctx) throws CommandFormatException {
if (!(SAVE.equals(getAction(ctx)))) {
return false;
}
return super.canAppearNext(ctx);
}
};
targetFile.addRequiredPreceding(operation);
overwrite = new ArgumentWithoutValue(this, "--overwrite") {
@Override
public boolean canAppearNext(CommandContext ctx) throws CommandFormatException {
if (!(SAVE.equals(getAction(ctx)))) {
return false;
}
return super.canAppearNext(ctx);
}
};
headers.addRequiredPreceding(operation);
}
@Override
protected void recognizeArguments(CommandContext ctx) throws CommandFormatException {
String act = getAction(ctx);
if (DISPLAY.equals(act)) {
if (targetFile.isPresent(ctx.getParsedCommandLine())) {
throw new CommandFormatException(targetFile.getFullName()
+ " can't be used with display action");
}
if (overwrite.isPresent(ctx.getParsedCommandLine())) {
throw new CommandFormatException(overwrite.getFullName()
+ " can't be used with display action");
}
}
super.recognizeArguments(ctx);
}
private String getAction(CommandContext ctx) {
final String originalLine = ctx.getParsedCommandLine().getOriginalLine();
if (originalLine == null || originalLine.isEmpty()) {
return null;
}
String[] words = originalLine.trim().split(" ");
String action = null;
boolean seenFirst = false;
for (String w : words) {
if (w.isEmpty()) {
continue;
}
if (!w.isEmpty()) {
if (seenFirst) {
action = w;
break;
} else {
seenFirst = true;
}
}
}
return action;
}
@Override
protected void handleResponse(CommandContext ctx, OperationResponse response,
boolean composite) throws CommandLineException {
ModelNode result = response.getResponseNode();
String targetPath = targetFile.getValue(ctx.getParsedCommandLine());
String act = action.getValue(ctx.getParsedCommandLine());
if (act == null || act.isEmpty()) {
throw new CommandFormatException("Action is missing");
}
AttachmentResponseHandler handler = new AttachmentResponseHandler(ctx,
targetPath,
act.equals(SAVE),
overwrite.isPresent(ctx.getParsedCommandLine()));
handler.handleResponse(result, response);
}
@Override
protected ModelNode buildRequestWithoutHeaders(CommandContext ctx)
throws CommandFormatException {
final String op = operation.getValue(ctx.getParsedCommandLine());
if (op == null) {
throw new CommandFormatException("Invalid operation");
}
ModelNode mn = ctx.buildRequest(op);
return mn;
}
private static class AttachmentResponseHandler implements ResponseHandler {
private final boolean save;
private final String targetPath;
private final CommandContext ctx;
private final boolean overwrite;
private AttachmentResponseHandler(CommandContext ctx, String targetPath,
boolean save, boolean overwrite) {
this.ctx = ctx;
this.targetPath = targetPath;
this.save = save;
this.overwrite = overwrite;
}
@Override
public void handleResponse(ModelNode step, OperationResponse response)
throws CommandLineException {
//First retrieve all uuid.
Set<String> uuids = getStreams(response.getResponseNode());
if (uuids.isEmpty()) {
return;
}
//Then lookup the complete data structure for a possible match.
Set<String> mystreams = new TreeSet<>();
// In case of a non composite operation, the headers are located
// inside the step ModelNode.
// So, although the operation wouldn't return the uuid in the result,
// we find the uuid in the headers.
// Obviously this would not work in batch mode (composite).
retrieveStreams(step, uuids, mystreams);
int index = 0;
for (String uuid : mystreams) {
if (save) {
index = saveStream(uuid, response, index);
} else {
displayStream(uuid, response);
}
}
}
private int saveStream(String uuid, OperationResponse response,
int index) throws CommandLineException {
String target = targetPath == null ? uuid : targetPath;
if (index > 0) {
target = target + "(" + index + ")";
}
OperationResponse.StreamEntry entry = response.getInputStream(uuid);
File targetFile = new File(target);
if (!overwrite) {
while (targetFile.exists()) {
String name = targetFile.getName();
int indexed = name.lastIndexOf("(");
if (indexed > 0) {
try {
String num = name.substring(indexed + 1, name.length() - 1);
index = Integer.valueOf(num);
index += 1;
} catch (NumberFormatException ex) {
// XXX OK, not a number.
}
} else {
index += 1;
}
targetFile = new File(targetFile.getAbsolutePath()
+ "(" + index + ")");
}
} else {
index += 1;
}
try {
Files.copy(
entry.getStream(),
targetFile.toPath(),
StandardCopyOption.REPLACE_EXISTING);
ctx.printLine("File saved to " + targetFile.getCanonicalPath());
} catch (IOException ex) {
throw new CommandLineException("Exception saving stream ", ex);
}
return index;
}
private void displayStream(String uuid, OperationResponse response)
throws CommandLineException {
OperationResponse.StreamEntry entry = response.getInputStream(uuid);
byte[] buffer = new byte[8 * 1024];
int bytesRead;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
while ((bytesRead = entry.getStream().read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
ctx.printLine("ATTACHMENT " + uuid + ":");
ctx.printLine(new String(out.toByteArray()));
} catch (IOException ex) {
throw new CommandLineException("Exception reading stream ", ex);
}
}
}
private static void retrieveStreams(ModelNode step, Set<String> uuids,
Set<String> mystreams) {
switch (step.getType()) {
case STRING: {
if (uuids.contains(step.asString())) {
mystreams.add(step.asString());
}
break;
}
case OBJECT: {
for (String key : step.keys()) {
ModelNode mn = step.get(key);
retrieveStreams(mn, uuids, mystreams);
}
break;
}
case LIST: {
for (int i = 0; i < step.asInt(); i++) {
ModelNode mn = step.get(i);
retrieveStreams(mn, uuids, mystreams);
}
break;
}
}
}
private static Set<String> getStreams(ModelNode response) {
Set<String> ret = new HashSet<>();
if (response.hasDefined(Util.RESPONSE_HEADERS)) {
ModelNode respHeaders = response.get(Util.RESPONSE_HEADERS);
if (respHeaders.hasDefined(Util.ATTACHED_STREAMS)) {
ModelNode attachments = respHeaders.get(Util.ATTACHED_STREAMS);
for (int i = 0; i < attachments.asInt(); i++) {
ModelNode attachment = attachments.get(i);
if (attachment.hasDefined(Util.UUID)) {
ret.add(attachment.get(Util.UUID).asString());
}
}
}
}
return ret;
}
@Override
public HandledRequest buildHandledRequest(CommandContext ctx,
Attachments attachments) throws CommandFormatException {
String targetPath = targetFile.getValue(ctx.getParsedCommandLine());
String act = action.getValue(ctx.getParsedCommandLine());
if (act == null || act.isEmpty()) {
throw new CommandFormatException("Action is missing");
}
AttachmentResponseHandler handler = new AttachmentResponseHandler(ctx,
targetPath,
act.equals(SAVE),
overwrite.isPresent(ctx.getParsedCommandLine()));
return new HandledRequest(buildRequest(ctx, attachments), handler);
}
}