package org.openntf.domino.logging; import java.io.IOException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.LogRecord; import org.openntf.domino.utils.Factory; public class LogHandlerFile extends FileHandler implements LogHandlerUpdateIF { private static class LHFConfig implements LogHandlerConfigIF { String pattern; int limit; int count; boolean append; LHFConfig() { pattern = null; limit = 100 * 1000 * 1000; count = 1; append = false; } @Override public boolean isEqual(final LogHandlerConfigIF o) { if (!(o instanceof LHFConfig)) return false; LHFConfig other = (LHFConfig) o; return append == other.append && limit == other.limit && count == other.count && pattern.equals(other.pattern); } } public static LogHandlerConfigIF configFromProps(final String props) { if (props == null) cfpError(props, "At least Pattern has to be specified"); LHFConfig ret = new LHFConfig(); String[] propArr = LogConfig.splitAlongComma(props); for (int i = 0; i < propArr.length; i++) { int ind = propArr[i].indexOf('='); while (ind > 0) { String propKey = propArr[i].substring(0, ind).trim(); String propValue = propArr[i].substring(ind + 1).trim(); if (propKey.isEmpty() || propValue.isEmpty()) { ind = -1; break; } if (propKey.equals("Limit")) { if ((ind = parsePosNum(propValue, 1000)) > 0) ret.limit = ind; break; } if (propKey.equals("Count")) { if ((ind = parsePosNum(propValue, 1)) > 0) ret.count = ind; break; } if (propKey.equals("Append")) { if (propValue.equals("true")) ret.append = true; else if (propValue.equals("false")) ret.append = false; else ind = -1; break; } if (propKey.equals("Pattern")) { ret.pattern = propValue.replace("<notesdata>", Factory.getDataPath()); if (ret.pattern.length() < 3) ind = -1; break; } ind = -1; break; } if (ind <= 0) cfpError(props, "Invalid Entry '" + propArr[i] + "'"); } if (ret.pattern == null) cfpError(props, "Missing entry 'Pattern'"); return ret; } private static int parsePosNum(final String propValue, final int min) { int ret = -1; try { ret = Integer.parseInt(propValue); if (ret < min) ret = -1; } catch (Exception e) { } return ret; } private static void cfpError(final String props, final String detail) { throw new IllegalArgumentException("Invalid Props-Property for LogHandlerFile (" + props + "): " + detail); } public static LogHandlerFile getInstance(final LogHandlerConfigIF config, final boolean useDefaultFormatter) throws IOException { if (!(config instanceof LHFConfig)) throw new IllegalArgumentException("Invalid call to LogHandlerFile.getInstance"); LogHandlerFile ret = new LogHandlerFile((LHFConfig) config); if (useDefaultFormatter) ret.setFormatter(new LogFormatterFileDefault()); return ret; } public LogHandlerFile(final LHFConfig config) throws IOException { super(config.pattern, config.limit, config.count, config.append); } @Override public boolean mayUpdateYourself(final LogHandlerConfigIF newHandlerConfig, final LogHandlerConfigIF oldHandlerConfig) { return newHandlerConfig.isEqual(oldHandlerConfig); } @Override public void doUpdateYourself(final LogHandlerConfigIF newhandlerConfig, final LogHandlerConfigIF oldHandlerConfig, final boolean useDefaultFormatter, final Formatter newFormatter) { if (newFormatter != null) setFormatter(newFormatter); else if (useDefaultFormatter && !(getFormatter() instanceof LogFormatterFileDefault)) setFormatter(new LogFormatterFileDefault()); } /** * * Calls the publish method of the parent FileHandler class * * Called from publish method via a PrivilegedAction to avoid access issues * * @param record * LogRecord to be output * @since org.openntf.domino 1.0.0 */ private void superPub(final LogRecord record) { super.publish(record); } /* * (non-Javadoc) * * @see java.util.logging.FileHandler#publish(java.util.logging.LogRecord) */ @Override public synchronized void publish(final LogRecord record) { if (publishing_.get() == Boolean.TRUE) return; publishing_.set(Boolean.TRUE); try { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { LogHandlerFile.this.superPub(record); LogHandlerFile.this.flush(); return null; } }); } catch (Throwable e) { System.err.println(e.toString()); e.printStackTrace(); } finally { publishing_.set(Boolean.FALSE); } } private static ThreadLocal<Boolean> publishing_ = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return Boolean.FALSE; } }; }