1 package org.dfdaemon.il2.core.logfile;
2
3 import org.apache.commons.beanutils.ConvertUtils;
4 import org.apache.commons.beanutils.Converter;
5 import org.apache.commons.logging.Log;
6 import org.apache.commons.logging.LogFactory;
7 import org.dfdaemon.il2.core.task.CoreTask;
8 import org.dfdaemon.il2.spi.event.EventProcessor;
9 import org.dfdaemon.il2.spi.event.EventProducer;
10 import org.dfdaemon.il2.api.event.Event;
11 import org.dfdaemon.il2.api.event.player.*;
12 import org.dfdaemon.il2.core.logfile.filter.DatedLineFilter;
13 import org.dfdaemon.il2.spi.logfile.LineFilter;
14 import org.dfdaemon.il2.core.logfile.filter.TimedLineFilter;
15 import org.joda.time.DateTime;
16 import org.springframework.beans.factory.annotation.Required;
17
18 import java.io.File;
19 import java.io.FileReader;
20 import java.io.LineNumberReader;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.atomic.AtomicBoolean;
23
24
25
26
27 public class FileLogParser implements EventProducer, CoreTask {
28
29 private final Log _log = LogFactory.getLog(this.getClass().getName());
30
31 private boolean _shouldSkip = true;
32 private long _pollDelay = 1000;
33 private long _retryDelay = 5000;
34 private File _file;
35
36 private EventProcessor _eventProcessor;
37 private DateTime _bias = new DateTime();
38 private final AtomicBoolean _alive = new AtomicBoolean(false);
39
40 private static LineFilter[] _logFilters;
41
42 {
43
44 ConvertUtils.register(new Converter() {
45 public Object convert(Class type, Object value) {
46 return MissionStateEvent.State.valueOf((String) value);
47 }
48 }, MissionStateEvent.State.class);
49
50
51 _logFilters = new LineFilter[]{
52
53 DatedLineFilter.createFilter("Mission: (.*) is Playing",
54 new String[]{"missionFileName"},
55 MissionPlayingEvent.class),
56
57 TimedLineFilter.createFilter("Mission (\\S+)",
58 new String[]{"state"},
59 MissionStateEvent.class),
60
61 TimedLineFilter.createFilter("(.+) in flight at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
62 new String[]{"who", "x", "y"},
63 TakeoffEvent.class),
64
65 TimedLineFilter.createFilter("(.+) shot down by landscape at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
66 new String[]{"who", "x", "y"},
67 CrashEvent.class),
68
69 TimedLineFilter.createFilter("(.+) landed at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
70 new String[]{"who", "x", "y"},
71 LandEvent.class),
72
73 TimedLineFilter.createFilter("(.+) damaged on the ground at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
74 new String[]{"who", "x", "y"},
75 DamageOnGroundEvent.class),
76
77 TimedLineFilter.createFilter("(.+) bailed out at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
78 new String[]{"who", "x", "y"},
79 BailOutEvent.class),
80
81 TimedLineFilter.createFilter("(.+) was captured at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
82 new String[]{"who", "x", "y"},
83 CaptureEvent.class),
84
85 TimedLineFilter.createFilter("(.+) was wounded at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
86 new String[]{"who", "x", "y"},
87 WoundEvent.class),
88
89 TimedLineFilter.createFilter("(.+) was heavily wounded at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
90 new String[]{"who", "x", "y"},
91 HeavyWoundEvent.class),
92
93 TimedLineFilter.createFilter("(.+) was killed at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
94 new String[]{"who", "x", "y"},
95 DeathEvent.class),
96
97 TimedLineFilter.createFilter("(.+) damaged by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
98 new String[]{"who", "byWho", "x", "y"},
99 DamageEvent.class),
100
101 TimedLineFilter.createFilter("(.+) was killed by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
102 new String[]{"who", "byWho", "x", "y"},
103 DeathEvent.class),
104
105 TimedLineFilter.createFilter("(.+) shot down by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
106 new String[]{"who", "byWho", "x", "y"},
107 DeathEvent.class),
108
109 TimedLineFilter.createFilter("(.+) was killed in his chute by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
110 new String[]{"who", "byWho", "x", "y"},
111 DeathEvent.class),
112
113 TimedLineFilter.createFilter("(.+) has chute destroyed by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
114 new String[]{"who", "byWho", "x", "y"},
115 DeathEvent.class),
116
117 TimedLineFilter.createFilter("(.+:\\S+) destroyed by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
118 new String[]{"who", "byWho", "x", "y"},
119 DeathEvent.class),
120
121 TimedLineFilter.createFilter("(.+) destroyed by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
122 new String[]{"what", "who", "x", "y"},
123 DestroyEvent.class),
124
125 TimedLineFilter.createFilter("(.+) loaded weapons \\'([^\\']+)\\' fuel (\\d+)%",
126 new String[]{"who", "weapon", "fuel"},
127 DestroyEvent.class),
128
129 TimedLineFilter.createFilter("(.+) turned wingtip smokes (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
130 new String[]{"who", "smokeState", "x", "y"},
131 WingtipSmokesEvent.class),
132
133 TimedLineFilter.createFilter("(.+) turned landing lights (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
134 new String[]{"who", "lightState", "x", "y"},
135 LandingLightsEvent.class),
136
137 TimedLineFilter.createFilter("(.+) entered refly menu",
138 new String[]{"who"},
139 ReflyMenuEvent.class),
140
141 TimedLineFilter.createFilter("(.+) seat occupied by (\\S+) at ([+-]?\\d+\\.\\d+) ([+-]?\\d+\\.\\d+)",
142 new String[]{"who", "byWho", "x", "y"},
143 OccupySeatEvent.class)
144 };
145 }
146
147
148
149
150
151
152
153 public void setRetryDelay(long retryDelay) {
154 _retryDelay = retryDelay;
155 }
156
157
158
159
160
161
162 public void setPollDelay(long pollDelay) {
163 _pollDelay = pollDelay;
164 }
165
166
167
168
169
170
171 public void setShouldSkip(boolean shouldSkip) {
172 _shouldSkip = shouldSkip;
173 }
174
175
176
177
178
179
180 @Required
181 public void setFile(File file) {
182 _file = file;
183 }
184
185
186
187
188
189
190 @Required
191 public void setEventProcessor(EventProcessor eventProcessor) {
192 _eventProcessor = eventProcessor;
193 }
194
195
196
197
198 public void preRun() throws Exception {
199 _alive.set(true);
200 while (!_file.exists()) {
201 if (_log.isErrorEnabled())
202 _log.error("File not found: " + _file.getAbsolutePath());
203 if (isTerminated()) {
204 if (_log.isInfoEnabled())
205 _log.info("Terminated");
206 throw new InterruptedException();
207 }
208
209 try {
210 TimeUnit.MILLISECONDS.sleep(_retryDelay);
211 } catch (InterruptedException e) {
212 if (_log.isInfoEnabled())
213 _log.info("Interrupted");
214 throw e;
215 }
216 }
217 if (_log.isInfoEnabled())
218 _log.debug("Initialized: " + _file);
219 }
220
221
222
223
224 public void run() throws Exception {
225
226
227
228 long size = _file.length();
229 if (_log.isDebugEnabled()) {
230 _log.debug("Initialized: " + _file.getAbsolutePath());
231 }
232
233 LineNumberReader reader = new LineNumberReader(new FileReader(_file));
234
235 if (_shouldSkip)
236 reader.skip(size);
237
238 if (_log.isDebugEnabled())
239 _log.debug("Start reading from: " + reader.getLineNumber() + " line.");
240
241 try {
242 outer:
243 while (!isTerminated()) {
244
245 while (!reader.ready()) {
246
247 size = _file.length();
248
249 if (isTerminated())
250 break outer;
251
252 Thread.sleep(_pollDelay);
253
254
255 if (_file.length() < size)
256 throw new IllegalStateException("File size changed" + _file.length() + "<" + size);
257 }
258
259 String line = reader.readLine();
260
261
262 Event event = null;
263 for (LineFilter filter : _logFilters) {
264 event = filter.handle(_bias, line);
265 if (event != null) {
266
267 _bias = event.getDate();
268 if (_log.isDebugEnabled()) {
269 _log.debug("Event:" + event);
270 }
271 _eventProcessor.inject(event);
272 break;
273 }
274 }
275 if (_log.isDebugEnabled() && event == null) {
276 _log.debug("Unknown line: " + line);
277 }
278 }
279 } finally {
280 if (_log.isInfoEnabled())
281 _log.debug("Finished");
282 reader.close();
283 }
284 }
285
286
287
288
289 public void afterRun() {
290 _alive.set(false);
291 }
292
293
294
295
296 public boolean stop() {
297 _alive.set(false);
298 return true;
299 }
300
301
302
303
304
305
306
307 public boolean isTerminated() {
308 return !_alive.get() || Thread.currentThread().isInterrupted();
309 }
310
311 }
312