ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/cms/source/server/uk/org/iscream/cms/server/client/WebFeeder.java
Revision: 1.21
Committed: Thu Jan 10 21:49:38 2002 UTC (22 years, 4 months ago) by tdb
Branch: MAIN
Branch point for: SERVER_PIRCBOT
Changes since 1.20: +75 -69 lines
Log Message:
Fix for the following tracker bug on SourceForge:
  [ #490762 ] WebFeeder crashes (NullPointer)

This bug occured when the directory for the webfeeder to place alerts in
didn't exist. Unlike the rest of the block of code, this section didn't
do any checks to make sure it existed before trying to get a listing of it.
This resulted in a null pointer exception. The fix is simple - a check is
done to make sure it's a directory before getting a listing. If it doesn't
exist a warning is logged to alert the user that there is a problem. It is
common for this directory not to exist if no alerts have yet be generated.

File Contents

# Content
1 //---PACKAGE DECLARATION---
2 package uk.org.iscream.cms.server.client;
3
4 //---IMPORTS---
5 import uk.org.iscream.cms.server.componentmanager.*;
6 import uk.org.iscream.cms.server.core.*;
7 import uk.org.iscream.cms.server.util.*;
8 import java.io.*;
9
10 /**
11 * Provides a feed to the webpage system.
12 *
13 * @author $Author: tdb $
14 * @version $Id: WebFeeder.java,v 1.20 2001/12/10 22:20:22 tdb Exp $
15 */
16 public class WebFeeder extends Thread {
17
18 //---FINAL ATTRIBUTES---
19
20 /**
21 * The current CVS revision of this class
22 */
23 public static final String REVISION = "$Revision: 1.20 $";
24
25 /**
26 * Default check period in seconds (30 minutes)
27 */
28 public final int DEFAULT_CHECK_PERIOD = 1800;
29
30 /**
31 * Delete alerts older than this in seconds, default.
32 */
33 public final int DEFAULT_AGE = 3600;
34
35 /**
36 * The default path seperator, here for convienience
37 */
38 private final String sep = File.separator;
39
40 //---STATIC METHODS---
41
42 /**
43 * Return a reference to the single class.
44 * Construct it if it does not already exist, otherwise just return the reference.
45 */
46 public synchronized static WebFeeder getInstance() {
47 if (_instance == null){
48 _instance = new WebFeeder();
49 }
50 return _instance;
51 }
52
53 //---CONSTRUCTORS---
54
55 /**
56 * Construct a new WebFeeder. This will also wipe out any
57 * old Alerts, as these can't be carried from one session
58 * until the next.
59 */
60 private WebFeeder() {
61 // do something, or nothing.. but must be private
62 // don't need to cleanup latest data
63
64 // -- cleanup old alerts
65 // get config proxy
66 ConfigurationProxy cp = ConfigurationProxy.getInstance();
67 // get file locations
68 String rootPath, alertSubDir, alertFileName;
69 try {
70 // work out where things are
71 rootPath = cp.getProperty("WebFeeder", "WebFeeder.rootPath");
72 alertSubDir = cp.getProperty("WebFeeder", "WebFeeder.alertSubDir");
73 File alertsDir = new File(rootPath, alertSubDir);
74 if(deleteContents(alertsDir)) {
75 _logger.write(this.toString(), Logger.DEBUG, "Deleted all files and directories from: "+rootPath+sep+alertSubDir);
76 } else {
77 _logger.write(this.toString(), Logger.WARNING, "Failed to delete all files and directories from: "+rootPath+sep+alertSubDir);
78 }
79 // cleanup complete
80 } catch (PropertyNotFoundException e) {
81 _logger.write(this.toString(), Logger.ERROR, "Failed to cleanup on construction, due to failing to get config for Alert Data: "+e);
82 // just leave it at that
83 }
84
85 // set our name and startup
86 setName("client.WebFeeder");
87 start();
88 }
89
90 //---PUBLIC METHODS---
91
92 /**
93 * Thread loop, will check at intervals for any files
94 * that need to be "cleaned up". This will normally
95 * be OK and FINAL alerts that have been around for longer
96 * than a specified period of time.
97 */
98 public void run() {
99 boolean running = true;
100 // get config proxy
101 ConfigurationProxy cp = ConfigurationProxy.getInstance();
102 // loop round
103 while(running) {
104 // get our check period
105 int checkPeriod = 0;
106 try {
107 checkPeriod = Integer.parseInt(cp.getProperty("WebFeeder", "WebFeeder.checkPeriod"));
108 } catch (NumberFormatException e) {
109 checkPeriod = DEFAULT_CHECK_PERIOD;
110 _logger.write(toString(), Logger.WARNING, "Erronous WebFeeder.checkPeriod value in configuration using default of " + checkPeriod + " seconds");
111 } catch (PropertyNotFoundException e) {
112 checkPeriod = DEFAULT_CHECK_PERIOD;
113 _logger.write(toString(), Logger.WARNING, "WebFeeder.checkPeriod value unavailable using default of " + checkPeriod + " seconds");
114 }
115 // wait for the check period
116 try {
117 Thread.sleep(checkPeriod * 1000);
118 } catch (InterruptedException e) {
119 }
120
121 // get alerts directory
122 String rootPath, alertSubDir, alertFileName;
123 try {
124 rootPath = cp.getProperty("WebFeeder", "WebFeeder.rootPath");
125 alertSubDir = cp.getProperty("WebFeeder", "WebFeeder.alertSubDir");
126 alertFileName = cp.getProperty("WebFeeder", "WebFeeder.alertFileName");
127 } catch (PropertyNotFoundException e) {
128 _logger.write(this.toString(), Logger.ERROR, "Failed to get config for Alert Data: "+e);
129 // bail out
130 continue;
131 }
132
133 // get the "age" barrier
134 int deleteOlderThan = 0;
135 try {
136 deleteOlderThan = Integer.parseInt(cp.getProperty("WebFeeder", "WebFeeder.alertDeleteOlderThan"));
137 } catch (NumberFormatException e) {
138 deleteOlderThan = DEFAULT_AGE;
139 _logger.write(toString(), Logger.WARNING, "Erronous WebFeeder.alertDeleteOlderThan value in configuration using default of " + deleteOlderThan + " seconds");
140 } catch (PropertyNotFoundException e) {
141 deleteOlderThan = DEFAULT_AGE;
142 _logger.write(toString(), Logger.WARNING, "WebFeeder.alertDeleteOlderThan value unavailable using default of " + deleteOlderThan + " seconds");
143 }
144
145 // list the files and delete as appropriate
146 File alertsDir = new File(rootPath, alertSubDir);
147 // check it's a directory
148 if(alertsDir.isDirectory()) {
149 // get all the hostnames directories
150 File[] contents = alertsDir.listFiles();
151 for(int i=0; i < contents.length; i++) {
152 // get a single directory from the array..
153 File hostdir = contents[i];
154 // ..and check it's a directory
155 if(hostdir.isDirectory()) {
156 // if this is set, we clean files older than it
157 long deleteFiles = -1;
158 // get all the contents of that directory
159 File[] hostdirContents = hostdir.listFiles();
160 for(int j=0; j < hostdirContents.length; j++) {
161 File alertFile = hostdirContents[j];
162 // get the filename..
163 String filename = alertFile.getName();
164 // ..and see if it ends with OK or FINAL
165 if(filename.endsWith(Alert.alertLevels[0]) ||
166 filename.endsWith(Alert.alertLevels[Alert.alertLevels.length-1])) {
167 // it does end with either OK or FINAL
168 // ... so we can check it for deletion
169 long lastModified = alertFile.lastModified();
170 long age = System.currentTimeMillis() - lastModified;
171 if(age > ((long) deleteOlderThan*1000)) {
172 // if we're on a final heartbeat, we probably want to
173 // clean up any stale alerts left behind
174 // by setting this flag, we'll clean them up on leaving this loop
175 if(filename.endsWith(".HB."+Alert.alertLevels[Alert.alertLevels.length-1])) {
176 // we do this so that delete files is set to the
177 // latest date of a HB.FINAL. There should only be
178 // one of them though :)
179 if(lastModified > deleteFiles) {
180 deleteFiles = lastModified;
181 }
182 }
183 // it's also older than our age to delete older than
184 if(!alertFile.delete()) {
185 _logger.write(this.toString(), Logger.WARNING, "Failed to delete the following 'old' alert file: "+alertFile.getPath());
186 }
187 }
188 }
189 }
190 // cleanup stale alerts
191 if(deleteFiles >= 0) {
192 File[] remainingHostdirContents = hostdir.listFiles();
193 for(int j=0; j < remainingHostdirContents.length; j++) {
194 File alertFile = remainingHostdirContents[j];
195 if(alertFile.lastModified() < deleteFiles) {
196 // alert file is older than the most recent
197 // FINAL Heartbeat alert.
198 if(alertFile.delete()) {
199 _logger.write(this.toString(), Logger.DEBUG, "Deleted stale alert file: "+alertFile.getPath());
200 }
201 else {
202 _logger.write(this.toString(), Logger.WARNING, "Failed to delete the following 'stale' alert file: "+alertFile.getPath());
203 }
204 }
205 }
206 }
207 // ---- RECAP ----
208 // at this point, we have cleaned up any OK or FINAL alerts
209 // that have passed our age limit. We have then cleaned up
210 // any alerts older the most recent Heartbeat FINAL alert,
211 // as these are probably stale. Any files left are valid and
212 // active alerts. We are now in a position to remove the host
213 // directory if it's empty.
214 // ---------------
215 // do a quick check to see if the directory is now empty
216 File[] newHostdirContents = hostdir.listFiles();
217 if(newHostdirContents.length == 0) {
218 // it does seem to be, try and delete it
219 // this will fail anyway if files still remain
220 if(!hostdir.delete()) {
221 _logger.write(this.toString(), Logger.WARNING, "Failed to delete the following empty host directory: "+hostdir.getPath());
222 }
223 }
224 }
225 }
226 }
227 else {
228 _logger.write(toString(), Logger.WARNING, "IO error reading alerts directory, maybe it doesn't exist? : " +rootPath+sep+alertSubDir);
229 }
230 }
231 }
232
233 /**
234 * Handles an XMLPacket. This will write it out to disk
235 * in an appropriate manner.
236 *
237 * @param packet the XMLPacket to write
238 */
239 public void receiveXMLPacket(XMLPacket packet) {
240 String packetType = packet.getParam("packet.attributes.type");
241 if(packetType == null || !packetType.equals("data")) {
242 // bail out, without warning
243 // this is probably a heartbeat or similar
244 return;
245 }
246 // get config proxy
247 ConfigurationProxy cp = ConfigurationProxy.getInstance();
248 // get file locations
249 String rootPath, latestSubDir, latestFileName;
250 try {
251 rootPath = cp.getProperty("WebFeeder", "WebFeeder.rootPath");
252 latestSubDir = cp.getProperty("WebFeeder", "WebFeeder.latestSubDir");
253 latestFileName = cp.getProperty("WebFeeder", "WebFeeder.latestFileName");
254 } catch (PropertyNotFoundException e) {
255 _logger.write(this.toString(), Logger.ERROR, "Failed to get config for Latest Data: "+e);
256 // bail out
257 return;
258 }
259 // get raw data
260 String data = packet.printAll();
261 String hostname = packet.getParam("packet.attributes.machine_name");
262 // set paths
263 File outDir = new File(rootPath, latestSubDir+sep+hostname);
264 File outFile = new File(rootPath, latestSubDir+sep+hostname+sep+latestFileName);
265 // write the data out
266 writeData(outDir, outFile, data);
267 }
268
269 /**
270 * Handles an Alert. This will write it out to disk
271 * in an appropriate manner.
272 *
273 * @param alert the Alert object to write
274 */
275 public void receiveAlert(Alert alert) {
276 // get config proxy
277 ConfigurationProxy cp = ConfigurationProxy.getInstance();
278 // get file locations
279 String rootPath, alertSubDir, alertFileName;
280 try {
281 rootPath = cp.getProperty("WebFeeder", "WebFeeder.rootPath");
282 alertSubDir = cp.getProperty("WebFeeder", "WebFeeder.alertSubDir");
283 alertFileName = cp.getProperty("WebFeeder", "WebFeeder.alertFileName");
284 } catch (PropertyNotFoundException e) {
285 _logger.write(this.toString(), Logger.ERROR, "Failed to get config for Alert Data: "+e);
286 // bail out
287 return;
288 }
289 // get raw data
290 String data = alert.printAll();
291 String hostname = alert.getSource();
292 // set paths
293 File outDir = new File(rootPath, alertSubDir+sep+hostname);
294 String destFile = alertSubDir+sep+hostname+sep+alertFileName+"."+String.valueOf(alert.getInitialAlertTime());
295 File outFile;
296 // check if we're at a special "end case" (OK or FINAL)
297 if(alert.getLevel()==0 || alert.getLevel()==Alert.alertLevels.length-1) {
298 if(alert.getAttributeName().equals("Heartbeat") && alert.getLevel()==Alert.alertLevels.length-1) {
299 // new file is something like alert.nnnnnnnn.HB.FINAL
300 outFile = new File(rootPath, destFile+".HB."+Alert.alertLevels[alert.getLevel()]);
301 } else {
302 // new file is something like alert.nnnnnnnn.OK
303 outFile = new File(rootPath, destFile+"."+Alert.alertLevels[alert.getLevel()]);
304 }
305 File oldFile = new File(rootPath, destFile);
306 if(!oldFile.renameTo(outFile)) {
307 _logger.write(this.toString(), Logger.WARNING, "Failed to rename old file, "+oldFile.getPath()+" to new file, "+outFile.getPath());
308 }
309 } else {
310 outFile = new File(rootPath, destFile);
311 }
312 // write the data out
313 writeData(outDir, outFile, data);
314 }
315
316 /**
317 * Overrides the {@link java.lang.Object#toString() Object.toString()}
318 * method to provide clean logging (every class should have this).
319 *
320 * This uses the uk.org.iscream.cms.server.util.FormatName class
321 * to format the toString()
322 *
323 * @return the name of this class and its CVS revision
324 */
325 public String toString() {
326 return FormatName.getName(
327 _name,
328 getClass().getName(),
329 REVISION);
330 }
331
332 //---PRIVATE METHODS---
333
334 /**
335 * Attempts to write "data" to "outFile" in "outDir".
336 *
337 * Performs checks to create the directories, and the file.
338 * Does not "return" anything to indicate failure or success.
339 *
340 * @param outDir the directory to put the file in
341 * @param outFile the filename to put the data in
342 * @param data the String of data to write
343 */
344 private void writeData(File outDir, File outFile, String data) {
345 // try to create directory
346 if(!outDir.exists()) {
347 if(!outDir.mkdirs()) {
348 // didn't exist, and we couldn't make it
349 _logger.write(this.toString(), Logger.ERROR, "Failed to create directory: "+outDir.getPath());
350 // bail out
351 return;
352 }
353 }
354 // directory has been made, check file exists
355 if(!outFile.exists()) {
356 try {
357 outFile.createNewFile();
358 } catch (IOException e) {
359 _logger.write(this.toString(), Logger.ERROR, "Failed to create file: "+e);
360 // bail out
361 return;
362 }
363 }
364 // file should now exist
365 if(outFile.canWrite()) {
366 PrintWriter out;
367 try {
368 out = new PrintWriter(new FileWriter(outFile));
369 out.println(data);
370 out.close();
371 } catch (IOException e) {
372 _logger.write(this.toString(), Logger.ERROR, "Failed to write file: "+e);
373 }
374 }
375 else {
376 _logger.write(this.toString(), Logger.ERROR, "File not writeable: "+outFile.getPath());
377 }
378 }
379
380 /**
381 * Iterates through dir (a directory) and deletes
382 * all files and subdirectories.
383 *
384 * @param dir the directory to clear
385 * @return true if it succeeded
386 */
387 private boolean deleteContents(File dir) {
388 boolean success = true;
389 if(dir.isDirectory()) {
390 // is a directory
391 File[] contents = dir.listFiles();
392 for(int i=0; i < contents.length; i++) {
393 File sub = contents[i];
394 if(sub.isDirectory()) {
395 // lets get recursive
396 success = success & deleteContents(sub);
397 }
398 // it's a file or empty dir
399 success = success & sub.delete();
400 }
401 }
402 else {
403 // not a directory?
404 success=false;
405 }
406 return success;
407 }
408
409 //---ACCESSOR/MUTATOR METHODS---
410
411 //---ATTRIBUTES---
412
413 /**
414 * This is the friendly identifier of the
415 * component this class is running in.
416 * eg, a Filter may be called "filter1",
417 * If this class does not have an owning
418 * component, a name from the configuration
419 * can be placed here. This name could also
420 * be changed to null for utility classes.
421 */
422 private String _name = ClientMain.NAME;
423
424 /**
425 * This holds a reference to the
426 * system logger that is being used.
427 */
428 private Logger _logger = ReferenceManager.getInstance().getLogger();
429
430 //---STATIC ATTRIBUTES---
431
432 /**
433 * A reference to the single instance of this class
434 */
435 private static WebFeeder _instance;
436
437 }