| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.util.logging; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedExceptionAction; |
| |
| import org.apache.harmony.logging.internal.nls.Messages; |
| |
| |
| /** |
| * A {@code Handler} put the description of log events into a cycled memory |
| * buffer. |
| * <p> |
| * Mostly this {@code MemoryHandler} just puts the given {@code LogRecord} into |
| * the internal buffer and doesn't perform any formatting or any other process. |
| * When the buffer is full, the earliest buffered records will be discarded. |
| * </p> |
| * <p> |
| * Every {@code MemoryHandler} has a target handler, and push action can be |
| * triggered so that all buffered records will be output to the target handler |
| * and normally the latter will publish the records. After the push action, the |
| * buffer will be cleared. |
| * </p> |
| * <p> |
| * The push action can be triggered in three ways: |
| * <ul> |
| * <li>The push method is called explicitly</li> |
| * <li>When a new {@code LogRecord} is put into the internal buffer, and it has |
| * a level which is not less than the specified push level.</li> |
| * <li>A subclass extends this {@code MemoryHandler} and call push method |
| * implicitly according to some criteria.</li> |
| * </ul> |
| * </p> |
| * <p> |
| * {@code MemoryHandler} will read following {@code LogManager} properties for |
| * initialization, if given properties are not defined or has invalid values, |
| * default value will be used. |
| * <ul> |
| * <li>java.util.logging.MemoryHandler.level specifies the level for this |
| * {@code Handler}, defaults to {@code Level.ALL}.</li> |
| * <li>java.util.logging.MemoryHandler.filter specifies the {@code Filter} |
| * class name, defaults to no {@code Filter}.</li> |
| * <li>java.util.logging.MemoryHandler.size specifies the buffer size in number |
| * of {@code LogRecord}, defaults to 1000.</li> |
| * <li>java.util.logging.MemoryHandler.push specifies the push level, defaults |
| * to level.SEVERE.</li> |
| * <li>java.util.logging.MemoryHandler.target specifies the class of the target |
| * {@code Handler}, no default value, which means this property must be |
| * specified either by property setting or by constructor.</li> |
| * </ul> |
| * </p> |
| */ |
| public class MemoryHandler extends Handler { |
| |
| //default maximum buffered number of LogRecord |
| private static final int DEFAULT_SIZE = 1000; |
| //target handler |
| private Handler target; |
| |
| //buffer size |
| private int size = DEFAULT_SIZE; |
| |
| //push level |
| private Level push = Level.SEVERE; |
| |
| //LogManager instance for convenience |
| private final LogManager manager = LogManager.getLogManager(); |
| |
| //buffer |
| private LogRecord[] buffer; |
| |
| //current position in buffer |
| private int cursor; |
| |
| /** |
| * Default constructor, construct and init a {@code MemoryHandler} using |
| * {@code LogManager} properties or default values. |
| * |
| * @throws RuntimeException |
| * if property value are invalid and no default value could be |
| * used. |
| */ |
| public MemoryHandler() { |
| super(); |
| String className = this.getClass().getName(); |
| //init target |
| final String targetName = manager.getProperty(className+".target"); //$NON-NLS-1$ |
| try { |
| Class<?> targetClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>(){ |
| public Class<?> run() throws Exception{ |
| ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
| if(loader == null){ |
| loader = ClassLoader.getSystemClassLoader(); |
| } |
| return loader.loadClass(targetName); |
| } |
| }); |
| target = (Handler) targetClass.newInstance(); |
| } catch (Exception e) { |
| // logging.10=Cannot load target handler:{0} |
| throw new RuntimeException(Messages.getString("logging.10", //$NON-NLS-1$ |
| targetName)); |
| } |
| //init size |
| String sizeString = manager.getProperty(className+".size"); //$NON-NLS-1$ |
| if (null != sizeString) { |
| try { |
| size = Integer.parseInt(sizeString); |
| if(size <= 0){ |
| size = DEFAULT_SIZE; |
| } |
| } catch (Exception e) { |
| printInvalidPropMessage(className+".size", sizeString, e); //$NON-NLS-1$ |
| } |
| } |
| //init push level |
| String pushName = manager.getProperty(className+".push"); //$NON-NLS-1$ |
| if (null != pushName) { |
| try { |
| push = Level.parse(pushName); |
| } catch (Exception e) { |
| printInvalidPropMessage(className+".push", pushName, e); //$NON-NLS-1$ |
| } |
| } |
| //init other properties which are common for all Handler |
| initProperties("ALL", null, "java.util.logging.SimpleFormatter", null); //$NON-NLS-1$//$NON-NLS-2$ |
| buffer = new LogRecord[size]; |
| } |
| |
| /** |
| * Construct and init a {@code MemoryHandler} using given target, size and |
| * push level, other properties using {@code LogManager} properties or |
| * default values. |
| * |
| * @param target |
| * the given {@code Handler} to output |
| * @param size |
| * the maximum number of buffered {@code LogRecord}, greater than |
| * zero |
| * @param pushLevel |
| * the push level |
| * @throws IllegalArgumentException |
| * if {@code size}<=0 |
| * @throws RuntimeException |
| * if property value are invalid and no default value could be |
| * used. |
| */ |
| public MemoryHandler(Handler target, int size, Level pushLevel) { |
| if (size <= 0) { |
| // logging.11=Size must be positive. |
| throw new IllegalArgumentException(Messages.getString("logging.11")); //$NON-NLS-1$ |
| } |
| target.getLevel(); |
| pushLevel.intValue(); |
| this.target = target; |
| this.size = size; |
| this.push = pushLevel; |
| initProperties("ALL", null, "java.util.logging.SimpleFormatter", null); //$NON-NLS-1$//$NON-NLS-2$ |
| buffer = new LogRecord[size]; |
| } |
| |
| /** |
| * Close this handler and target handler, free all associated resources. |
| * |
| * @throws SecurityException |
| * if security manager exists and it determines that caller does |
| * not have the required permissions to control this handler. |
| */ |
| @Override |
| public void close() { |
| manager.checkAccess(); |
| target.close(); |
| setLevel(Level.OFF); |
| } |
| |
| /** |
| * Call target handler to flush any buffered output. Note that this doesn't |
| * cause this {@code MemoryHandler} to push. |
| */ |
| @Override |
| public void flush() { |
| target.flush(); |
| } |
| |
| /** |
| * Put a given {@code LogRecord} into internal buffer. If given record is |
| * not loggable, just return. Otherwise it is stored in the buffer. |
| * Furthermore if the record's level is not less than the push level, the |
| * push action is triggered to output all the buffered records to the target |
| * handler, and the target handler will publish them. |
| * |
| * @param record |
| * the log record |
| */ |
| @Override |
| public synchronized void publish(LogRecord record) { |
| if (!isLoggable(record)) { |
| return; |
| } |
| if (cursor >= size) { |
| cursor = 0; |
| } |
| buffer[cursor++] = record; |
| if (record.getLevel().intValue() >= push.intValue()) { |
| push(); |
| } |
| return; |
| } |
| |
| /** |
| * Return the push level. |
| * |
| * @return the push level |
| */ |
| public Level getPushLevel() { |
| return push; |
| } |
| |
| /** |
| * <p> |
| * Check if given {@code LogRecord} would be put into this |
| * {@code MemoryHandler}'s internal buffer. |
| * </p> |
| * <p> |
| * The given {@code LogRecord} is loggable if and only if it has appropriate |
| * level and it pass any associated filter's check. |
| * </p> |
| * <p> |
| * Note that the push level is not used for this check. |
| * </p> |
| * |
| * @param record |
| * the given {@code LogRecord} |
| * @return the given {@code LogRecord} if it should be logged, {@code false} |
| * if {@code LogRecord} is {@code null}. |
| */ |
| @Override |
| public boolean isLoggable(LogRecord record) { |
| return super.isLoggable(record); |
| } |
| |
| /** |
| * Triggers a push action to output all buffered records to the target handler, |
| * and the target handler will publish them. Then the buffer is cleared. |
| */ |
| public void push() { |
| for (int i = cursor; i < size; i++) { |
| if(null != buffer[i]) { |
| target.publish(buffer[i]); |
| } |
| buffer[i] = null; |
| } |
| for (int i = 0; i < cursor; i++) { |
| if(null != buffer[i]) { |
| target.publish(buffer[i]); |
| } |
| buffer[i] = null; |
| } |
| cursor = 0; |
| } |
| |
| /** |
| * Set the push level. The push level is used to check the push action |
| * triggering. When a new {@code LogRecord} is put into the internal |
| * buffer and its level is not less than the push level, the push action |
| * will be triggered. Note that set new push level won't trigger push action. |
| * |
| * @param newLevel |
| * the new level to set. |
| * @throws SecurityException |
| * if security manager exists and it determines that caller |
| * does not have the required permissions to control this handler. |
| */ |
| public void setPushLevel(Level newLevel) { |
| manager.checkAccess(); |
| newLevel.intValue(); |
| this.push = newLevel; |
| } |
| } |