package org.araqne.logstorage.dump.engine; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.araqne.codec.FastEncodingRule; import org.araqne.logstorage.Log; import org.araqne.logstorage.LogStorage; import org.araqne.logstorage.LogTraverseCallback; import org.araqne.logstorage.LogTraverseCallback.Sink; import org.araqne.logstorage.TableScanRequest; import org.araqne.logstorage.dump.DumpManifest; import org.araqne.logstorage.dump.DumpService; import org.araqne.logstorage.dump.ExportRequest; import org.araqne.logstorage.dump.DumpTabletKey; import org.araqne.logstorage.dump.ExportTabletTask; import org.araqne.logstorage.dump.ExportTask; import org.araqne.logstorage.dump.ExportWorker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LocalExportWorker implements ExportWorker { private final Logger slog = LoggerFactory.getLogger(LocalExportWorker.class); private ExportRequest req; private ExportTask task; private DumpService dumpService; private LogStorage storage; private FileOutputStream fos; private ZipOutputStream zos; private BufferedOutputStream bos; private File path; public LocalExportWorker(ExportRequest req, DumpService dumpService, LogStorage storage) { this.req = req; this.dumpService = dumpService; this.storage = storage; this.task = new ExportTask(req); this.path = new File(req.getParams().get("path")); } @Override public ExportTask getTask() { return task; } @Override public void run() { DumpManifest manifest = new DumpManifest(); manifest.setDriverType("local"); slog.info("araqne logstorage: start export estimation [{}]", req.getGuid()); List<ExportTabletTask> tabletTasks = dumpService.estimate(req); task.setEstimationDone(); Map<DumpTabletKey, ExportTabletTask> m = task.getTabletTasks(); for (ExportTabletTask t : tabletTasks) { m.put(new DumpTabletKey(t.getTableName(), t.getDay()), t); manifest.getTables().put(t.getTableName(), t.getTableId()); } slog.info("araqne logstorage: start export job [{}]", req.getGuid()); try { fos = new FileOutputStream(path); zos = new ZipOutputStream(fos); bos = new BufferedOutputStream(zos, 8192 * 4); String lastTableName = null; SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); for (ExportTabletTask t : tabletTasks) { if (task.isCancelled()) return; if (lastTableName == null || !lastTableName.equals(t.getTableName())) { zos.putNextEntry(new ZipEntry(t.getTableId() + "/")); lastTableName = t.getTableName(); } String entryPath = t.getTableId() + "/" + df.format(t.getDay()) + ".dmp"; zos.putNextEntry(new ZipEntry(entryPath)); OutputLoader loader = new OutputLoader(new OutputSink(0, 0), t); TableScanRequest scanReq = new TableScanRequest(); scanReq.setTableName(t.getTableName()); scanReq.setFrom(t.getDay()); scanReq.setTo(nextDay(t.getDay())); scanReq.setUseSerialScan(true); scanReq.setAsc(true); scanReq.setTraverseCallback(loader); try { storage.search(scanReq); } catch (InterruptedException e) { } t.setActualCount(loader.count); t.setCompleted(true); manifest.getEntries().add(t.toEntry()); bos.flush(); zos.closeEntry(); } zos.putNextEntry(new ZipEntry("manifest.json")); zos.write(manifest.toJSON().getBytes("utf-8")); zos.closeEntry(); } catch (Throwable t) { task.setFailureException(t); slog.error("araqne logstorage: export job [" + req.getGuid() + "] failed", t); } finally { ensureClose(bos); ensureClose(zos); ensureClose(fos); slog.info("araqne logstorage: export job [{}] completed", req.getGuid()); if (task.isCancelled()) { path.delete(); } } } private Date nextDay(Date d) { Calendar c = Calendar.getInstance(); c.setTime(d); c.add(Calendar.DAY_OF_MONTH, 1); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return c.getTime(); } private void ensureClose(Closeable c) { try { if (c != null) c.close(); } catch (IOException e) { } } private class OutputSink extends Sink { public OutputSink(long offset, long limit) { super(offset, limit); } @Override protected void processLogs(List<Log> logs) { FastEncodingRule enc = new FastEncodingRule(); List<Object> l = new ArrayList<Object>(); for (Log log : logs) { Map<String, Object> data = log.getData(); data.put("_time", log.getDate()); l.add(data); } try { ByteBuffer bb = enc.encode(l); int len = bb.array().length; byte[] b = new byte[4]; b[0] = (byte) ((len >> 24) & 0xff); b[1] = (byte) ((len >> 16) & 0xff); b[2] = (byte) ((len >> 8) & 0xff); b[3] = (byte) (len & 0xff); bos.write(b); bos.write(bb.array()); } catch (IOException e) { slog.error("araqne logstorage: output failure", e); } } } private class OutputLoader extends LogTraverseCallback { private long count = 0; private ExportTabletTask tabletTask; public OutputLoader(Sink sink, ExportTabletTask tabletTask) { super(sink); this.tabletTask = tabletTask; } @Override public void interrupt() { } @Override public boolean isInterrupted() { return task.isCancelled(); } @Override protected List<Log> filter(List<Log> logs) { if (logs.isEmpty()) return logs; count += logs.size(); tabletTask.setActualCount(count); return logs; } } }