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
20import java.io.UnsupportedEncodingException;
21import java.nio.charset.Charset;
22
23/**
24 * A {@code Handler} object accepts a logging request and exports the desired
25 * messages to a target, for example, a file, the console, etc. It can be
26 * disabled by setting its logging level to {@code Level.OFF}.
27 */
28public abstract class Handler {
29
30    private static final Level DEFAULT_LEVEL = Level.ALL;
31
32    // the error manager to report errors during logging
33    private ErrorManager errorMan;
34
35    // the character encoding used by this handler
36    private String encoding;
37
38    // the logging level
39    private Level level;
40
41    // the formatter used to export messages
42    private Formatter formatter;
43
44    // the filter used to filter undesired messages
45    private Filter filter;
46
47    // class name, used for property reading
48    private String prefix;
49
50    /**
51     * Constructs a {@code Handler} object with a default error manager instance
52     * {@code ErrorManager}, the default encoding, and the default logging
53     * level {@code Level.ALL}. It has no filter and no formatter.
54     */
55    protected Handler() {
56        this.errorMan = new ErrorManager();
57        this.level = DEFAULT_LEVEL;
58        this.encoding = null;
59        this.filter = null;
60        this.formatter = null;
61        this.prefix = this.getClass().getName();
62    }
63
64    // get a instance from given class name, using Class.forName()
65    private Object getDefaultInstance(String className) {
66        Object result = null;
67        if (className == null) {
68            return result;
69        }
70        try {
71            result = Class.forName(className).newInstance();
72        } catch (Exception e) {
73            // ignore
74        }
75        return result;
76    }
77
78    // get a instance from given class name, using context classloader
79    private Object getCustomizeInstance(final String className) throws Exception {
80        ClassLoader loader = Thread.currentThread().getContextClassLoader();
81        if (loader == null) {
82            loader = ClassLoader.getSystemClassLoader();
83        }
84        Class<?> c = loader.loadClass(className);
85        return c.newInstance();
86    }
87
88    // print error message in some format
89    void printInvalidPropMessage(String key, String value, Exception e) {
90        String msg = "Invalid property value for " + prefix + ":" + key + "/" + value;
91        errorMan.error(msg, e, ErrorManager.GENERIC_FAILURE);
92    }
93
94    /**
95     * init the common properties, including filter, level, formatter, and
96     * encoding
97     */
98    void initProperties(String defaultLevel, String defaultFilter,
99            String defaultFormatter, String defaultEncoding) {
100        LogManager manager = LogManager.getLogManager();
101
102        // set filter
103        final String filterName = manager.getProperty(prefix + ".filter");
104        if (filterName != null) {
105            try {
106                filter = (Filter) getCustomizeInstance(filterName);
107            } catch (Exception e1) {
108                printInvalidPropMessage("filter", filterName, e1);
109                filter = (Filter) getDefaultInstance(defaultFilter);
110            }
111        } else {
112            filter = (Filter) getDefaultInstance(defaultFilter);
113        }
114
115        // set level
116        String levelName = manager.getProperty(prefix + ".level");
117        if (levelName != null) {
118            try {
119                level = Level.parse(levelName);
120            } catch (Exception e) {
121                printInvalidPropMessage("level", levelName, e);
122                level = Level.parse(defaultLevel);
123            }
124        } else {
125            level = Level.parse(defaultLevel);
126        }
127
128        // set formatter
129        final String formatterName = manager.getProperty(prefix + ".formatter");
130        if (formatterName != null) {
131            try {
132                formatter = (Formatter) getCustomizeInstance(formatterName);
133            } catch (Exception e) {
134                printInvalidPropMessage("formatter", formatterName, e);
135                formatter = (Formatter) getDefaultInstance(defaultFormatter);
136            }
137        } else {
138            formatter = (Formatter) getDefaultInstance(defaultFormatter);
139        }
140
141        // set encoding
142        final String encodingName = manager.getProperty(prefix + ".encoding");
143        try {
144            internalSetEncoding(encodingName);
145        } catch (UnsupportedEncodingException e) {
146            printInvalidPropMessage("encoding", encodingName, e);
147        }
148    }
149
150    /**
151     * Closes this handler. A flush operation will be performed and all the
152     * associated resources will be freed. Client applications should not use
153     * this handler after closing it.
154     */
155    public abstract void close();
156
157    /**
158     * Flushes any buffered output.
159     */
160    public abstract void flush();
161
162    /**
163     * Accepts a logging request and sends it to the the target.
164     *
165     * @param record
166     *            the log record to be logged; {@code null} records are ignored.
167     */
168    public abstract void publish(LogRecord record);
169
170    /**
171     * Gets the character encoding used by this handler, {@code null} for
172     * default encoding.
173     *
174     * @return the character encoding used by this handler.
175     */
176    public String getEncoding() {
177        return this.encoding;
178    }
179
180    /**
181     * Gets the error manager used by this handler to report errors during
182     * logging.
183     *
184     * @return the error manager used by this handler.
185     */
186    public ErrorManager getErrorManager() {
187        LogManager.getLogManager().checkAccess();
188        return this.errorMan;
189    }
190
191    /**
192     * Gets the filter used by this handler.
193     *
194     * @return the filter used by this handler (possibly {@code null}).
195     */
196    public Filter getFilter() {
197        return this.filter;
198    }
199
200    /**
201     * Gets the formatter used by this handler to format the logging messages.
202     *
203     * @return the formatter used by this handler (possibly {@code null}).
204     */
205    public Formatter getFormatter() {
206        return this.formatter;
207    }
208
209    /**
210     * Gets the logging level of this handler, records with levels lower than
211     * this value will be dropped.
212     *
213     * @return the logging level of this handler.
214     */
215    public Level getLevel() {
216        return this.level;
217    }
218
219    /**
220     * Determines whether the supplied log record needs to be logged. The
221     * logging levels will be checked as well as the filter.
222     *
223     * @param record
224     *            the log record to be checked.
225     * @return {@code true} if the supplied log record needs to be logged,
226     *         otherwise {@code false}.
227     */
228    public boolean isLoggable(LogRecord record) {
229        if (record == null) {
230            throw new NullPointerException("record == null");
231        }
232        if (this.level.intValue() == Level.OFF.intValue()) {
233            return false;
234        } else if (record.getLevel().intValue() >= this.level.intValue()) {
235            return this.filter == null || this.filter.isLoggable(record);
236        }
237        return false;
238    }
239
240    /**
241     * Reports an error to the error manager associated with this handler,
242     * {@code ErrorManager} is used for that purpose. No security checks are
243     * done, therefore this is compatible with environments where the caller
244     * is non-privileged.
245     *
246     * @param msg
247     *            the error message, may be {@code null}.
248     * @param ex
249     *            the associated exception, may be {@code null}.
250     * @param code
251     *            an {@code ErrorManager} error code.
252     */
253    protected void reportError(String msg, Exception ex, int code) {
254        this.errorMan.error(msg, ex, code);
255    }
256
257    /**
258     * Sets the character encoding used by this handler. A {@code null} value
259     * indicates the use of the default encoding. This internal method does
260     * not check security.
261     *
262     * @param newEncoding
263     *            the character encoding to set.
264     * @throws UnsupportedEncodingException
265     *             if the specified encoding is not supported by the runtime.
266     */
267    void internalSetEncoding(String newEncoding) throws UnsupportedEncodingException {
268        // accepts "null" because it indicates using default encoding
269        if (newEncoding == null) {
270            this.encoding = null;
271        } else {
272            if (Charset.isSupported(newEncoding)) {
273                this.encoding = newEncoding;
274            } else {
275                throw new UnsupportedEncodingException(newEncoding);
276            }
277        }
278    }
279
280    /**
281     * Sets the character encoding used by this handler, {@code null} indicates
282     * a default encoding.
283     *
284     * @throws UnsupportedEncodingException if {@code charsetName} is not supported.
285     */
286    public void setEncoding(String charsetName) throws UnsupportedEncodingException {
287        LogManager.getLogManager().checkAccess();
288        internalSetEncoding(charsetName);
289    }
290
291    /**
292     * Sets the error manager for this handler.
293     *
294     * @param newErrorManager
295     *            the error manager to set.
296     * @throws NullPointerException
297     *             if {@code em} is {@code null}.
298     */
299    public void setErrorManager(ErrorManager newErrorManager) {
300        LogManager.getLogManager().checkAccess();
301        if (newErrorManager == null) {
302            throw new NullPointerException("newErrorManager == null");
303        }
304        this.errorMan = newErrorManager;
305    }
306
307    /**
308     * Sets the filter to be used by this handler.
309     *
310     * @param newFilter
311     *            the filter to set, may be {@code null}.
312     */
313    public void setFilter(Filter newFilter) {
314        LogManager.getLogManager().checkAccess();
315        this.filter = newFilter;
316    }
317
318    /**
319     * Sets the formatter to be used by this handler. This internal method does
320     * not check security.
321     *
322     * @param newFormatter
323     *            the formatter to set.
324     */
325    void internalSetFormatter(Formatter newFormatter) {
326        if (newFormatter == null) {
327            throw new NullPointerException("newFormatter == null");
328        }
329        this.formatter = newFormatter;
330    }
331
332    /**
333     * Sets the formatter to be used by this handler.
334     *
335     * @param newFormatter
336     *            the formatter to set.
337     * @throws NullPointerException
338     *             if {@code newFormatter} is {@code null}.
339     */
340    public void setFormatter(Formatter newFormatter) {
341        LogManager.getLogManager().checkAccess();
342        internalSetFormatter(newFormatter);
343    }
344
345    /**
346     * Sets the logging level of the messages logged by this handler, levels
347     * lower than this value will be dropped.
348     *
349     * @param newLevel
350     *            the logging level to set.
351     * @throws NullPointerException
352     *             if {@code newLevel} is {@code null}.
353     */
354    public void setLevel(Level newLevel) {
355        if (newLevel == null) {
356            throw new NullPointerException("newLevel == null");
357        }
358        LogManager.getLogManager().checkAccess();
359        this.level = newLevel;
360    }
361}
362