package org.rhq.core.pc.drift;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.common.drift.ChangeSetReader;
import org.rhq.common.drift.FileEntry;
import org.rhq.common.drift.Headers;
import org.rhq.core.domain.drift.DriftFile;
import org.rhq.core.util.stream.StreamUtil;
public class DriftFilesSender implements Runnable {
private Log log = LogFactory.getLog(DriftFilesSender.class);
private int resourceId;
private Headers headers;
private List<? extends DriftFile> driftFiles;
private ChangeSetManager changeSetMgr;
private DriftClient driftClient;
public void setResourceId(int resourceId) {
this.resourceId = resourceId;
}
public void setHeaders(Headers headers) {
this.headers = headers;
}
public void setDriftFiles(List<? extends DriftFile> driftFiles) {
this.driftFiles = driftFiles;
}
public void setDriftClient(DriftClient driftClient) {
this.driftClient = driftClient;
}
public void setChangeSetManager(ChangeSetManager changeSetManager) {
changeSetMgr = changeSetManager;
}
@Override
public void run() {
ZipOutputStream stream = null;
int numContentFiles = 0;
try {
if (log.isInfoEnabled()) {
log.info("Preparing to send content to server for " + defToString());
}
long startTime = System.currentTimeMillis();
File changeSet = changeSetMgr.findChangeSet(resourceId, headers.getDriftDefinitionName());
File changeSetDir = changeSet.getParentFile();
// Note that the content file has a specific format that the server
// expects. The file name is of the form content_<token>.zip where
// token is a unique string that the agent can use to identify the
// content zip file when the server sends an acknowledgement. The
// token is necessary because it is possible, albeit not likely,
// for a a particular definition to wind up with multiple content
// zip files and there is no guarantee that they will be acked in
// the order in which they were sent. The name of each file in the
// zip file should be the SHA for that file. The server uses that
// SHA to look up the DriftFile object it has already created for
// the content.
//
// jsanda
String timestamp = Long.toString(System.currentTimeMillis());
String contentFileName = "content_" + timestamp + ".zip";
final File zipFile = new File(changeSetDir, contentFileName);
stream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
if (driftFiles.size() == 1) {
DriftFile driftFile = driftFiles.get(0);
File file = find(driftFile);
if (file == null || !file.exists()) {
log.warn("Unable to find file for " + driftFile);
} else {
if (log.isDebugEnabled()) {
log.debug("Adding " + file.getPath() + " to " + contentFileName);
}
addFileToContentZipFile(stream, driftFile, file);
++numContentFiles;
}
} else {
Map<String, FileEntry> fileEntries = createSnapshotIndex();
for (DriftFile driftFile : driftFiles) {
FileEntry entry = fileEntries.get(driftFile.getHashId());
if (entry == null) {
continue;
}
File file = new File(headers.getBasedir(), entry.getFile());
if (file == null || !file.exists()) {
log.warn("Unable to find file for " + driftFile);
} else {
if (log.isDebugEnabled()) {
log.debug("Adding " + file.getPath() + " to " + contentFileName);
}
addFileToContentZipFile(stream, driftFile, file);
++numContentFiles;
}
}
}
if (numContentFiles > 0) {
driftClient.sendChangeSetContentToServer(resourceId, headers.getDriftDefinitionName(), zipFile);
}
stream.close();
stream = null;
if (log.isInfoEnabled()) {
long endTime = System.currentTimeMillis();
log.info("Finished submitting request to send content to server in " + (endTime - startTime) +
" ms");
}
} catch (IOException e) {
if (numContentFiles > 0) {
// Only log an error message if the content zip file is not empty. With
// Java 6, closing an empty ZipOutputStream causes an exception which we
// can ignore. On Java 7 however, no exception is thrown when closing an
// empty ZipOutputStream. This check keeps the error reporting logic
// consistent across Java 6 and 7 such that we only log an error message
// when we fail to close the output stream when the content zip file is
// not empty. See https://bugzilla.redhat.com/show_bug.cgi?id=838681 for
// more info.
//
// jsanda
log.error("Failed to send drift files.", e);
}
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
}
private void addFileToContentZipFile(ZipOutputStream stream, DriftFile driftFile, File file) throws IOException {
FileInputStream fis = new FileInputStream(file);
try {
stream.putNextEntry(new ZipEntry(driftFile.getHashId()));
StreamUtil.copy(fis, stream, false);
} finally {
fis.close();
}
}
private File find(DriftFile driftFile) throws IOException {
ChangeSetReader reader = changeSetMgr.getChangeSetReader(resourceId, headers.getDriftDefinitionName());
try {
for (FileEntry entry : reader) {
if (entry.getNewSHA().equals(driftFile.getHashId())) {
return new File(headers.getBasedir(), entry.getFile());
}
}
return null;
} finally {
reader.close();
}
}
private Map<String, FileEntry> createSnapshotIndex() throws IOException {
ChangeSetReader reader = changeSetMgr.getChangeSetReader(resourceId, headers.getDriftDefinitionName());
try {
Map<String, FileEntry> map = new TreeMap<String, FileEntry>();
for (FileEntry entry : reader) {
map.put(entry.getNewSHA(), entry);
}
return map;
} finally {
reader.close();
}
}
private String defToString() {
return "[resourceId: " + resourceId + ", driftDefinitionId: " + headers.getDriftDefinitionId() +
", driftDefinitionName: " + headers.getDriftDefinitionName() + "]";
}
}