/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat Middleware LLC, 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.repository;
import static org.jboss.as.protocol.mgmt.ProtocolUtils.expectHeader;
import java.io.BufferedOutputStream;
import java.io.DataInput;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import org.jboss.as.protocol.StreamUtils;
import org.jboss.as.protocol.mgmt.ActiveOperation;
import org.jboss.as.protocol.mgmt.FlushableDataOutput;
import org.jboss.as.protocol.mgmt.ManagementRequestContext;
import org.jboss.as.protocol.mgmt.ManagementResponseHeader;
import org.jboss.as.protocol.mgmt.RequestProcessingException;
import org.jboss.logging.BasicLogger;
/**
* Common protocol code for getting files from master->slave HC and HC->server.
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
*/
public abstract class RemoteFileRequestAndHandler {
private final RemoteFileProtocolIdMapper protocol;
private final Executor asyncExecutor;
protected RemoteFileRequestAndHandler(RemoteFileProtocolIdMapper protocol) {
this(protocol, null);
}
protected RemoteFileRequestAndHandler(RemoteFileProtocolIdMapper protocol, Executor asyncExecutor) {
this.protocol = protocol;
this.asyncExecutor = asyncExecutor;
}
public void sendRequest(FlushableDataOutput output, byte rootId, String filePath) throws IOException{
output.writeByte(protocol.paramRootId());
output.writeByte(rootId);
output.writeByte(protocol.paramFilePath());
output.writeUTF(filePath);
}
public void handleResponse(DataInput input, File localPath, BasicLogger log, ActiveOperation.ResultHandler<File> resultHandler, ManagementRequestContext<Void> context)
throws IOException, CannotCreateLocalDirectoryException, DidNotReadEntireFileException{
expectHeader(input, protocol.paramNumFiles());
int numFiles = input.readInt();
log.debugf("Received %d files for %s", numFiles, localPath);
switch (numFiles) {
case -1: { // Not found on DC
break;
}
case 0: { // Found on DC, but was an empty dir
if (!localPath.mkdirs()) {
throw new CannotCreateLocalDirectoryException(localPath);
}
break;
}
default: { // Found on DC
for (int i = 0; i < numFiles; i++) {
expectHeader(input, protocol.fileStart());
expectHeader(input, protocol.paramFilePath());
final String path = input.readUTF();
expectHeader(input, protocol.paramFileSize());
final long length = input.readLong();
log.debugf("Received file [%s] of length %d", path, length);
final File file = new File(localPath, path);
if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) {
throw new CannotCreateLocalDirectoryException(localPath.getParentFile());
}
if(length == 0L) {
file.mkdir();
} else {
long totalRead = 0;
try (OutputStream fileOut = new BufferedOutputStream(new FileOutputStream(file))) {
final byte[] buffer = new byte[8192];
while (totalRead < length) {
int len = Math.min((int) (length - totalRead), buffer.length);
input.readFully(buffer, 0, len);
fileOut.write(buffer, 0, len);
totalRead += len;
}
}
if (totalRead != length) {
throw new DidNotReadEntireFileException((length - totalRead));
}
}
expectHeader(input, protocol.fileEnd());
}
}
}
resultHandler.done(localPath);
}
public void handleRequest(final DataInput input, final RootFileReader reader,
final ActiveOperation.ResultHandler<Void> resultHandler,
final ManagementRequestContext<Void> context) throws IOException {
expectHeader(input, protocol.paramRootId());
final byte rootId = input.readByte();
expectHeader(input, protocol.paramFilePath());
final String filePath = input.readUTF();
ManagementRequestContext.AsyncTask<Void> task = new ManagementRequestContext.AsyncTask<Void>() {
@Override
public void execute(ManagementRequestContext<Void> context) throws RequestProcessingException, IOException {
final File localPath = reader.readRootFile(rootId, filePath);
FlushableDataOutput output = context.writeMessage(ManagementResponseHeader.create(context.getRequestHeader()));
try {
writeResponse(localPath, output);
output.close();
resultHandler.done(null); // call stack (AsyncTaskRunner created by ManagementRequestContext) handles failures
} finally {
StreamUtils.safeClose(output);
}
}
};
if (asyncExecutor == null) {
context.executeAsync(task);
} else {
context.executeAsync(task, asyncExecutor);
}
}
private void writeResponse(final File localPath, final FlushableDataOutput output) throws IOException {
output.writeByte(protocol.paramNumFiles());
if (localPath == null || !localPath.exists()) {
output.writeInt(-1);
} else if (localPath.isFile()) {
output.writeInt(1);
writeFile(localPath, localPath, output);
} else {
final List<File> childFiles = getChildFiles(localPath);
output.writeInt(childFiles.size());
for (File child : childFiles) {
writeFile(localPath, child, output);
}
}
}
private List<File> getChildFiles(final File base) {
final List<File> childFiles = new ArrayList<>();
getChildFiles(base, childFiles);
return childFiles;
}
private void getChildFiles(final File base, final List<File> childFiles) {
for (File child : base.listFiles()) {
childFiles.add(child);
if (child.isFile() || isEmpty(child)) {
childFiles.add(child);
} else {
getChildFiles(child, childFiles);
}
}
}
private boolean isEmpty(File file) {
return file.isDirectory() && (file.list() == null || file.list().length == 0);
}
private String getRelativePath(final File parent, final File child) {
return child.getAbsolutePath().substring(parent.getAbsolutePath().length()+1);
}
private void writeFile(final File localPath, final File file, final FlushableDataOutput output) throws IOException {
output.writeByte(protocol.fileStart());
output.writeByte(protocol.paramFilePath());
output.writeUTF(getRelativePath(localPath, file));
output.writeByte(protocol.paramFileSize());
if (file.isDirectory()) {
output.writeLong(0L);
output.writeByte(protocol.fileEnd());
return;
} else {
output.writeLong(file.length());
}
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
byte[] buffer = new byte[8192];
int len;
while ((len = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {
}
}
}
output.writeByte(protocol.fileEnd());
}
/**
* Maps the expected protocol codes to the actual protocol
*/
public interface RemoteFileProtocolIdMapper {
byte paramRootId();
byte paramNumFiles();
byte fileStart();
byte paramFilePath();
byte paramFileSize();
byte fileEnd();
}
/**
* Reads the root file being got
*/
public interface RootFileReader {
File readRootFile(byte rootId, String filePath) throws RequestProcessingException;
}
/**
* Indicates a directory could not be created
*/
public static class CannotCreateLocalDirectoryException extends Exception {
private static final long serialVersionUID = 1L;
final File dir;
private CannotCreateLocalDirectoryException(File dir) {
this.dir = dir;
}
public File getDir() {
return dir;
}
}
/**
* Indicates a file was not completely read
*/
public static class DidNotReadEntireFileException extends Exception {
private static final long serialVersionUID = 1L;
final long missing;
private DidNotReadEntireFileException(long missing) {
this.missing = missing;
}
public long getMissing() {
return missing;
}
}
}