1 package com.trendmicro.grid.acl.ds.bclog;
2
3 import com.trendmicro.grid.acl.commons.Paths;
4 import com.trendmicro.grid.acl.l0.datatypes.ProcessPackageDataSet;
5 import org.springframework.stereotype.Service;
6
7 import javax.annotation.PostConstruct;
8 import javax.annotation.PreDestroy;
9 import java.io.*;
10 import java.util.Calendar;
11 import java.util.Date;
12
13
14
15
16
17
18
19 @Service
20 public class BinaryChangeLogWriterService {
21
22 public static final String CHARSET = "UTF-8";
23 public static final int LOGS_PER_HOUR = 6;
24 public static final String PATH_PATTERN = "%1$tY-%1$tm/%1$td/changes-%1$tY-%1$tm-%1$tdT%1$tH-%1$tM-%1$tS.zip";
25
26 long currentTimeOffset;
27 Calendar calendar = Calendar.getInstance();
28 BinaryChangeLogFileWriter currentWriter;
29 FileDescriptor tempLogDescriptor;
30 File changeLogPath, latestLogPointer, tempLog, prevLog, currentLog;
31
32 @PostConstruct
33 public synchronized void initializePaths() throws IOException {
34 currentTimeOffset = currentLogTimeOffset();
35 changeLogPath = new File(Paths.getResourcePath(), "binary-changelogs");
36 if (!changeLogPath.isDirectory() && !changeLogPath.mkdirs())
37 throw new RuntimeException("Failed creating required directory '" + changeLogPath + "'");
38
39 latestLogPointer = new File(changeLogPath, "latest.txt");
40
41 String latestLogPath = getLatestLogPointerValue();
42 if (latestLogPath != null)
43 currentLog = new File(changeLogPath, latestLogPath);
44 updateLogFiles();
45 }
46
47 @PreDestroy
48 public synchronized void closeAndPublishTempLog() throws IOException {
49 if (tempLog != null) {
50 if (currentWriter != null) {
51 currentWriter.close();
52 currentWriter = null;
53 }
54
55 if (currentLog.isFile())
56 mergeLogs(tempLog, currentLog);
57 else {
58 File parentFile = currentLog.getParentFile();
59 if (!parentFile.isDirectory() && !parentFile.mkdirs())
60 throw new IOException(parentFile + " is not a directory, cannot store binary changelog.");
61
62 if (!tempLog.renameTo(currentLog))
63 throw new IOException("Failed to rename '" + tempLog + "' to '" + currentLog + "'");
64
65
66 updateLatestLogPointerValue();
67
68 updateLogFiles();
69 }
70 }
71 }
72
73 public synchronized void fsync() throws IOException {
74 if (currentWriter != null)
75 currentWriter.flush();
76 if (tempLogDescriptor != null && tempLogDescriptor.valid())
77 tempLogDescriptor.sync();
78 }
79
80 void updateLogFiles() {
81 long nextOffset = currentLogTimeOffset();
82 if (nextOffset > currentTimeOffset) {
83 prevLog = currentLog;
84 currentTimeOffset = nextOffset;
85 currentLog = new File(changeLogPath, String.format(PATH_PATTERN, new Date(nextOffset)));
86 }
87 }
88
89 String getLatestLogPointerValue() throws IOException {
90 if (!latestLogPointer.isFile())
91 return null;
92 BufferedReader reader = new BufferedReader(
93 new InputStreamReader(new FileInputStream(latestLogPointer), CHARSET));
94 try {
95 return reader.readLine();
96 } finally {
97 reader.close();
98 }
99 }
100
101 void updateLatestLogPointerValue() throws IOException {
102 PrintWriter pOut = new PrintWriter(latestLogPointer, CHARSET);
103 try {
104 pOut.println(extractReferencePath(currentLog));
105 } finally {
106 pOut.close();
107 }
108 }
109
110 private long currentLogTimeOffset() {
111 calendar.setTimeInMillis(System.currentTimeMillis());
112 calendar.set(Calendar.MILLISECOND, 0);
113 calendar.set(Calendar.SECOND, 0);
114
115
116 double div = 60D / LOGS_PER_HOUR;
117 int minute = (int) Math.floor((double) calendar.get(Calendar.MINUTE) / div);
118 calendar.set(Calendar.MINUTE, (int) (minute * div));
119
120 return calendar.getTimeInMillis();
121 }
122
123 private synchronized BinaryChangeLogFileWriter getWriter() throws IOException {
124 if (tempLog == null || currentWriter == null || System.currentTimeMillis() > currentTimeOffset) {
125 closeAndPublishTempLog();
126 tempLog = new File(Paths.getTempPath(), "current-changelog.zip");
127 String[] refs = prevLog != null ? new String[]{extractReferencePath(prevLog)} : new String[0];
128 FileOutputStream stream = new FileOutputStream(tempLog);
129 tempLogDescriptor = stream.getFD();
130 currentWriter = new BinaryChangeLogFileWriter(new BufferedOutputStream(stream), refs);
131 }
132
133 return currentWriter;
134 }
135
136 String extractReferencePath(File file) {
137 String path = changeLogPath.toURI().toString(), filePath = file.toURI().toString();
138 if (!filePath.startsWith(path))
139 throw new IllegalArgumentException(filePath + " is not below " + path);
140 return filePath.substring(path.length());
141 }
142
143 public synchronized void writeNext(ProcessPackageDataSet dataSet) throws IOException {
144 getWriter().writeNext(dataSet);
145 }
146
147 public synchronized void mergeLogs(File source, File target) throws IOException {
148
149
150 }
151 }