/*
* #%L
* GarethHealy :: JBoss Fuse Examples :: ActiveMQ Playground :: Client POC
* %%
* Copyright (C) 2013 - 2016 Gareth Healy
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package com.garethahealy.activemq.client.poc.errorstrategys;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BodyToFileErrorStrategy implements AmqErrorStrategy<String[]> {
//NOTE: Cases not covered:
// 1. If file cannot be written, message is lost.
// 2. Writes are not batched, possible performance issue with high throughput.
// 3. File is not rotated on size, only via date.
// 4. File is written as a simple CSV
// 5. Locking tactic is aggressive in that it lasts for longer periods than is probably needed, but is simpler
private static final Logger LOG = LoggerFactory.getLogger(BodyToFileErrorStrategy.class);
private static final String HIDDEN_DIRECTORY = ".complete";
private final ReentrantLock lock = new ReentrantLock();
private Charset utf8Charset = Charset.forName("UTF8");
private String pathToPersistenceStore;
private int maxFileExistRetrys = Integer.MAX_VALUE / 2;
public BodyToFileErrorStrategy(String pathToPersistenceStore) {
this.pathToPersistenceStore = pathToPersistenceStore;
}
public List<String[]> getBackedupLines(String queueName) {
List<String[]> answer = new ArrayList<String[]>();
try {
lock.lock();
Collection<File> backupFiles = getListOfBackupFiles(queueName);
LOG.debug("Found {} backup files", backupFiles.size());
for (File current : backupFiles) {
answer.addAll(readFile(current));
moveFileToHiddenDirectory(current);
}
} catch (IOException ex) {
LOG.error("Exception getting backed up lines for queue:{} because {}", queueName, ExceptionUtils.getStackTrace(ex));
} finally {
lock.unlock();
}
return answer;
}
@Override
public void handle(Throwable ex, String queueName, Object[] body) {
LOG.error("Exception producing message {} to queue:{} because {}", body, queueName, ExceptionUtils.getStackTrace(ex));
Collection<String> lines = new ArrayList<String>();
lines.add(String.format("%s,%s", body[0], body[1]));
try {
lock.lock();
URL backupUrl = getFullPathAndFileName(queueName);
File backupFile = FileUtils.toFile(backupUrl);
LOG.warn("Attempting to write body {} to {}", body, backupUrl.toExternalForm());
//Touch file so we know it exists and have permissions
FileUtils.touch(backupFile);
//Write to disk
writeLinesToFile(backupFile, lines);
} catch (IOException caughtex) {
LOG.error("Exception handling body to persistence store for queue:{} because {}", queueName, ExceptionUtils.getStackTrace(caughtex));
} finally {
lock.unlock();
}
}
private void writeLinesToFile(File backupFile, Collection<String> lines) throws IOException {
FileUtils.writeLines(backupFile, utf8Charset.name(), lines, null, true);
}
private URL getFullPathAndFileName(String queueName) throws MalformedURLException {
String fileUri = String.format("file:%s/%s", pathToPersistenceStore, getFileName(queueName));
LOG.debug("Got backup file as {}", fileUri);
return new URL(FilenameUtils.separatorsToSystem(fileUri));
}
private String getFileName(String queueName) {
DateTime now = DateTime.now(DateTimeZone.UTC);
String date = now.toString(ISODateTimeFormat.date());
return String.format("%s_%s.csv", queueName, date);
}
private Collection<File> getListOfBackupFiles(String queueName) throws IOException {
checkBackupDirectory();
String fileUri = String.format("file:%s", pathToPersistenceStore);
File directory = FileUtils.toFile(new URL(FilenameUtils.separatorsToSystem(fileUri)));
IOFileFilter filter = FileFilterUtils.prefixFileFilter(String.format("%s_", queueName), IOCase.INSENSITIVE);
LOG.debug("Looking for backup files in {} using filter {}", fileUri, filter);
return FileUtils.listFiles(directory, filter, null);
}
private void checkBackupDirectory() throws IOException {
String fileUri = String.format("file:%s%s.amq", pathToPersistenceStore, File.separatorChar);
File tempFile = FileUtils.toFile(new URL(FilenameUtils.separatorsToSystem(fileUri)));
LOG.debug("About to touch {}", fileUri);
FileUtils.touch(tempFile);
}
private List<String[]> readFile(File file) throws IOException {
List<String[]> lines = new ArrayList<String[]>();
List<String> linesInFile = FileUtils.readLines(file, Charset.forName("UTF8"));
for (String line : linesInFile) {
String[] lineSplit = line.split(",");
lines.add(lineSplit);
}
return lines;
}
private void moveFileToHiddenDirectory(File source) throws IOException {
boolean exists = true;
int i = 0;
while (exists) {
File backup = FileUtils.toFile(getHiddenDirectoryPathForFile(source, String.valueOf(i)));
try {
FileUtils.moveFile(source, backup);
exists = false;
} catch (FileExistsException ex) {
LOG.error("Exception moving file to hidden folder because {}. Retrying...", ex.getMessage());
exists = true;
}
i++;
if (i > maxFileExistRetrys) {
throw new IOException(String.format("moveFileToHiddenDirectory has hit %s for %s" + maxFileExistRetrys, source.getName()));
}
}
}
private URL getHiddenDirectoryPathForFile(File source, String retry) throws MalformedURLException {
String path = FilenameUtils.getFullPath(source.getAbsolutePath());
String fileName = FilenameUtils.getName(source.getAbsolutePath());
String fileUri = String.format("file:%s%s%s%s%s", path, HIDDEN_DIRECTORY, File.separatorChar, retry, fileName);
LOG.debug("Got hidden directory as {}", fileUri);
return new URL(FilenameUtils.separatorsToSystem(fileUri));
}
}