1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.logging;
19
20/**
21 * A {@code Handler} put the description of log events into a cycled memory
22 * buffer.
23 * <p>
24 * Mostly this {@code MemoryHandler} just puts the given {@code LogRecord} into
25 * the internal buffer and doesn't perform any formatting or any other process.
26 * When the buffer is full, the earliest buffered records will be discarded.
27 * <p>
28 * Every {@code MemoryHandler} has a target handler, and push action can be
29 * triggered so that all buffered records will be output to the target handler
30 * and normally the latter will publish the records. After the push action, the
31 * buffer will be cleared.
32 * <p>
33 * The push method can be called directly, but will also be called automatically
34 * if a new <code>LogRecord</code> is added that has a level greater than or
35 * equal to than the value defined for the property
36 * java.util.logging.MemoryHandler.push.
37 * <p>
38 * {@code MemoryHandler} will read following {@code LogManager} properties for
39 * initialization, if given properties are not defined or has invalid values,
40 * default value will be used.
41 * <ul>
42 * <li>java.util.logging.MemoryHandler.filter specifies the {@code Filter}
43 * class name, defaults to no {@code Filter}.</li>
44 * <li>java.util.logging.MemoryHandler.level specifies the level for this
45 * {@code Handler}, defaults to {@code Level.ALL}.</li>
46 * <li>java.util.logging.MemoryHandler.push specifies the push level, defaults
47 * to level.SEVERE.</li>
48 * <li>java.util.logging.MemoryHandler.size specifies the buffer size in number
49 * of {@code LogRecord}, defaults to 1000.</li>
50 * <li>java.util.logging.MemoryHandler.target specifies the class of the target
51 * {@code Handler}, no default value, which means this property must be
52 * specified either by property setting or by constructor.</li>
53 * </ul>
54 */
55public class MemoryHandler extends Handler {
56
57    // default maximum buffered number of LogRecord
58    private static final int DEFAULT_SIZE = 1000;
59
60    // target handler
61    private Handler target;
62
63    // buffer size
64    private int size = DEFAULT_SIZE;
65
66    // push level
67    private Level push = Level.SEVERE;
68
69    // LogManager instance for convenience
70    private final LogManager manager = LogManager.getLogManager();
71
72    // buffer
73    private LogRecord[] buffer;
74
75    // current position in buffer
76    private int cursor;
77
78    /**
79     * Default constructor, construct and init a {@code MemoryHandler} using
80     * {@code LogManager} properties or default values.
81     *
82     * @throws RuntimeException
83     *             if property value are invalid and no default value could be
84     *             used.
85     */
86    public MemoryHandler() {
87        String className = this.getClass().getName();
88        // init target
89        final String targetName = manager.getProperty(className + ".target");
90        try {
91            ClassLoader loader = Thread.currentThread().getContextClassLoader();
92            if (loader == null) {
93                loader = ClassLoader.getSystemClassLoader();
94            }
95            Class<?> targetClass = loader.loadClass(targetName);
96            target = (Handler) targetClass.newInstance();
97        } catch (Exception e) {
98            throw new RuntimeException("Cannot load target handler '" + targetName + "'");
99        }
100        // init size
101        String sizeString = manager.getProperty(className + ".size");
102        if (sizeString != null) {
103            try {
104                size = Integer.parseInt(sizeString);
105                if (size <= 0) {
106                    size = DEFAULT_SIZE;
107                }
108            } catch (Exception e) {
109                printInvalidPropMessage(className + ".size", sizeString, e);
110            }
111        }
112        // init push level
113        String pushName = manager.getProperty(className + ".push");
114        if (pushName != null) {
115            try {
116                push = Level.parse(pushName);
117            } catch (Exception e) {
118                printInvalidPropMessage(className + ".push", pushName, e);
119            }
120        }
121        // init other properties which are common for all Handler
122        initProperties("ALL", null, "java.util.logging.SimpleFormatter", null);
123        buffer = new LogRecord[size];
124    }
125
126    /**
127     * Construct and init a {@code MemoryHandler} using given target, size and
128     * push level, other properties using {@code LogManager} properties or
129     * default values.
130     *
131     * @param target
132     *            the given {@code Handler} to output
133     * @param size
134     *            the maximum number of buffered {@code LogRecord}, greater than
135     *            zero
136     * @param pushLevel
137     *            the push level
138     * @throws IllegalArgumentException
139     *             if {@code size <= 0}
140     * @throws RuntimeException
141     *             if property value are invalid and no default value could be
142     *             used.
143     */
144    public MemoryHandler(Handler target, int size, Level pushLevel) {
145        if (size <= 0) {
146            throw new IllegalArgumentException("size <= 0");
147        }
148        target.getLevel();
149        pushLevel.intValue();
150        this.target = target;
151        this.size = size;
152        this.push = pushLevel;
153        initProperties("ALL", null, "java.util.logging.SimpleFormatter", null);
154        buffer = new LogRecord[size];
155    }
156
157    /**
158     * Close this handler and target handler, free all associated resources.
159     */
160    @Override
161    public void close() {
162        manager.checkAccess();
163        target.close();
164        setLevel(Level.OFF);
165    }
166
167    /**
168     * Call target handler to flush any buffered output. Note that this doesn't
169     * cause this {@code MemoryHandler} to push.
170     */
171    @Override
172    public void flush() {
173        target.flush();
174    }
175
176    /**
177     * Put a given {@code LogRecord} into internal buffer. If given record is
178     * not loggable, just return. Otherwise it is stored in the buffer.
179     * Furthermore if the record's level is not less than the push level, the
180     * push action is triggered to output all the buffered records to the target
181     * handler, and the target handler will publish them.
182     *
183     * @param record
184     *            the log record
185     */
186    @Override public synchronized void publish(LogRecord record) {
187        if (!isLoggable(record)) {
188            return;
189        }
190        if (cursor >= size) {
191            cursor = 0;
192        }
193        buffer[cursor++] = record;
194        if (record.getLevel().intValue() >= push.intValue()) {
195            push();
196        }
197    }
198
199    /**
200     * Return the push level.
201     *
202     * @return the push level
203     */
204    public Level getPushLevel() {
205        return push;
206    }
207
208    /**
209     * Check if given {@code LogRecord} would be put into this
210     * {@code MemoryHandler}'s internal buffer.
211     * <p>
212     * The given {@code LogRecord} is loggable if and only if it has appropriate
213     * level and it pass any associated filter's check.
214     * <p>
215     * Note that the push level is not used for this check.
216     *
217     * @param record
218     *            the given {@code LogRecord}
219     * @return the given {@code LogRecord} if it should be logged, {@code false}
220     *         if {@code LogRecord} is {@code null}.
221     */
222    @Override
223    public boolean isLoggable(LogRecord record) {
224        return super.isLoggable(record);
225    }
226
227    /**
228     * Triggers a push action to output all buffered records to the target handler,
229     * and the target handler will publish them. Then the buffer is cleared.
230     */
231    public void push() {
232        for (int i = cursor; i < size; i++) {
233            if (buffer[i] != null) {
234                target.publish(buffer[i]);
235            }
236            buffer[i] = null;
237        }
238        for (int i = 0; i < cursor; i++) {
239            if (buffer[i] != null) {
240                target.publish(buffer[i]);
241            }
242            buffer[i] = null;
243        }
244        cursor = 0;
245    }
246
247    /**
248     * Set the push level. The push level is used to check the push action
249     * triggering. When a new {@code LogRecord} is put into the internal
250     * buffer and its level is not less than the push level, the push action
251     * will be triggered. Note that set new push level won't trigger push action.
252     *
253     * @param newLevel
254     *                 the new level to set.
255     */
256    public void setPushLevel(Level newLevel) {
257        manager.checkAccess();
258        newLevel.intValue();
259        this.push = newLevel;
260    }
261}
262