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   * TODO: Create Description.
15   *
16   * @author juergen_kellerer, 2010-05-28
17   * @version 1.0
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  				// Update the pointer to the latest file.
66  				updateLatestLogPointerValue();
67  				// Update the files now
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 		// Rounding the time to hit the hours: 00, 10, 20, 30, 40, 50
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 		// TODO: Merge 2 log files together (can happen if the ACL is stopped and
149 		// TODO: started within the log-file timeframe)
150 	}
151 }