You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2015/09/28 19:49:01 UTC
[2/2] tomee git commit: backporting TOMEE-1631 - Rotating JUL Handler
backporting TOMEE-1631 - Rotating JUL Handler
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/da4df975
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/da4df975
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/da4df975
Branch: refs/heads/tomee-1.7.x
Commit: da4df975e5caf6d022788c6f9fe605c2cc197d4e
Parents: 8afa54e
Author: Romain Manni-Bucau <rm...@gmail.com>
Authored: Mon Sep 28 10:48:52 2015 -0700
Committer: Romain Manni-Bucau <rm...@gmail.com>
Committed: Mon Sep 28 10:48:52 2015 -0700
----------------------------------------------------------------------
tomee/tomee-juli/pom.xml | 27 +
.../org/apache/juli/logging/LogFactory.java | 133 -----
.../org/apache/tomee/TomEELogConfigurer.java | 96 ---
.../jul/formatter/AsyncConsoleHandler.java | 34 +-
.../tomee/jul/formatter/log/JULLogger.java | 179 ++++++
.../tomee/jul/formatter/log/LoggerFactory.java | 23 +
.../tomee/jul/formatter/log/NoopLogger.java | 117 ++++
.../tomee/jul/formatter/log/ReloadableLog.java | 111 ++++
.../tomee/jul/formatter/log/TomEELog.java | 164 +++++
.../handler/rotating/BackgroundTaskRunner.java | 91 +++
.../tomee/jul/handler/rotating/Duration.java | 178 ++++++
.../jul/handler/rotating/LocalFileHandler.java | 593 +++++++++++++++++++
.../apache/tomee/jul/handler/rotating/Size.java | 343 +++++++++++
.../jul/handler/rotating/ArchivingTest.java | 215 +++++++
.../handler/rotating/LocalFileHandlerTest.java | 128 ++++
.../tomee/jul/handler/rotating/PerfRunner.java | 109 ++++
16 files changed, 2311 insertions(+), 230 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/pom.xml
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/pom.xml b/tomee/tomee-juli/pom.xml
index 3c57d80..bc578f8 100644
--- a/tomee/tomee-juli/pom.xml
+++ b/tomee/tomee-juli/pom.xml
@@ -45,8 +45,35 @@
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${jmh.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
+ <properties>
+ <jmh.version>1.10.5</jmh.version>
+ </properties>
+
<build>
<plugins>
<plugin>
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/juli/logging/LogFactory.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/juli/logging/LogFactory.java b/tomee/tomee-juli/src/main/java/org/apache/juli/logging/LogFactory.java
deleted file mode 100644
index 019b45f..0000000
--- a/tomee/tomee-juli/src/main/java/org/apache/juli/logging/LogFactory.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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 org.apache.juli.logging;
-
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.logging.LogManager;
-
-
-// tomcat doesn't have any spi mecanism so forking this class
-public /* abstract */ class LogFactory {
- public static final String FACTORY_PROPERTY =
- "org.apache.commons.logging.LogFactory";
-
- public static final String FACTORY_DEFAULT =
- "org.apache.commons.logging.impl.LogFactoryImpl";
-
- public static final String FACTORY_PROPERTIES =
- "commons-logging.properties";
-
- public static final String HASHTABLE_IMPLEMENTATION_PROPERTY =
- "org.apache.commons.logging.LogFactory.HashtableImpl";
-
- private static LogFactory singleton=new LogFactory();
- private final Collection<String> names = new HashSet<String>();
-
- Properties logConfig;
-
- protected LogFactory() {
- logConfig=new Properties();
- }
-
- protected static void setSingleton(final LogFactory singleton) {
- if (singleton == null) {
- return;
- }
- LogFactory.singleton = singleton;
- }
-
- void setLogConfig( final Properties p ) {
- this.logConfig=p;
- }
-
- public synchronized Collection<String> getNames() {
- return names;
- }
-
- public synchronized Log getInstance(final String name) throws LogConfigurationException {
- names.add(name);
- return DirectJDKLog.getInstance(name);
- }
-
- public void release() {
- DirectJDKLog.release();
- }
-
- public Object getAttribute(final String name) {
- return logConfig.get(name);
- }
-
- public String[] getAttributeNames() {
- final String[] result = new String[logConfig.size()];
- return logConfig.keySet().toArray(result);
- }
-
- public void removeAttribute(final String name) {
- logConfig.remove(name);
- }
-
- public void setAttribute(final String name, final Object value) {
- logConfig.put(name, value);
- }
-
- public Log getInstance(final Class<?> clazz)
- throws LogConfigurationException {
- return getInstance( clazz.getName());
- }
-
- public static LogFactory getFactory() throws LogConfigurationException {
- return singleton;
- }
-
- public static Log getLog(final Class<?> clazz)
- throws LogConfigurationException {
- return getFactory().getInstance(clazz);
-
- }
-
- public static Log getLog(final String name)
- throws LogConfigurationException {
- return getFactory().getInstance(name);
-
- }
-
- public static void release(final ClassLoader classLoader) {
- // JULI's log manager looks at the current classLoader so there is no
- // need to use the passed in classLoader, the default implementation
- // does not so calling reset in that case will break things
- if (!LogManager.getLogManager().getClass().getName().equals(
- "java.util.logging.LogManager")) {
- LogManager.getLogManager().reset();
- }
- }
-
- public static void releaseAll() {
- singleton.release();
- }
-
- public static String objectId(final Object o) {
- if (o == null) {
- return "null";
- } else {
- return o.getClass().getName() + "@" + System.identityHashCode(o);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/TomEELogConfigurer.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/TomEELogConfigurer.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/TomEELogConfigurer.java
deleted file mode 100644
index e62ac08..0000000
--- a/tomee/tomee-juli/src/main/java/org/apache/tomee/TomEELogConfigurer.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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 org.apache.tomee;
-
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collection;
-
-public abstract class TomEELogConfigurer extends LogFactory {
- public static void configureLogs() {
- if (Boolean.getBoolean("tomee.skip-tomcat-log")) {
- return;
- }
-
- final Thread thread = Thread.currentThread();
- try {
- final ClassLoader tccl = thread.getContextClassLoader(); // this is in classpath not StandardClassLoader so use reflection
- final Class<?> logger = tccl.loadClass("org.apache.openejb.util.Logger");
- final Method m = logger.getDeclaredMethod("delegateClass");
- final String clazz = (String) m.invoke(null);
- final LogFactory factory;
- if ("org.apache.openejb.util.Log4jLogStreamFactory".equals(clazz)) {
- factory = LogFactory.class.cast(tccl.loadClass("org.apache.tomee.loader.log.Log4jLogFactory").newInstance());
- } else if ("org.apache.openejb.util.Slf4jLogStreamFactory".equals(clazz)) {
- factory = LogFactory.class.cast(tccl.loadClass("org.apache.tomee.loader.log.Slf4jLogFactory").newInstance());
- } else {
- factory = null;
- }
- if (factory != null) {
- final LogFactory oldFactory = getFactory();
- final Collection<String> names = new ArrayList<String>(oldFactory.getNames());
- oldFactory.getNames().clear();
- oldFactory.release();
- setSingleton(factory);
- reload(factory, tccl, names);
- }
- } catch (final Throwable th) {
- System.err.println(th.getClass().getName() + ": " + th.getMessage());
- }
- }
-
- private static void reload(final LogFactory factory, final ClassLoader tccl, final Collection<String> names) {
- for (final String name : names) {
- try {
- final Field f = Class.forName(name, false, tccl).getDeclaredField("log");
- if (!Log.class.equals(f.getType())) {
- continue;
- }
-
- final boolean acc = f.isAccessible();
- f.setAccessible(true);
-
- final Log newValue = factory.getInstance(name);
- final int modifiers = f.getModifiers();
- if (Modifier.isFinal(modifiers)) {
- final Field modifiersField = Field.class.getDeclaredField("modifiers");
- modifiersField.setAccessible(true);
- modifiersField.setInt(f, modifiers & ~Modifier.FINAL);
-
- f.set(null, newValue);
-
- modifiersField.setInt(f, modifiers & Modifier.FINAL);
- } else {
- f.set(null, newValue);
- }
-
- f.setAccessible(acc);
- } catch (final Throwable e) {
- // no-op
- }
- }
- }
-
- private TomEELogConfigurer() {
- // no-op
- }
-}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/AsyncConsoleHandler.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/AsyncConsoleHandler.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/AsyncConsoleHandler.java
index 4eeb1e8..c6724c0 100644
--- a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/AsyncConsoleHandler.java
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/AsyncConsoleHandler.java
@@ -18,13 +18,45 @@ package org.apache.tomee.jul.formatter;
import org.apache.juli.AsyncFileHandler;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
import java.util.logging.LogRecord;
public class AsyncConsoleHandler extends AsyncFileHandler {
- private final ConsoleHandler delegate = new ConsoleHandler();
+ private final ConsoleHandler delegate = new ConsoleHandler() {{
+ setFormatter(new SingleLineFormatter()); // console -> dev. File uses plain old format
+ }};
protected void publishInternal(final LogRecord record) {
delegate.publish(record);
}
+
+ // copy cause of classloading
+ private static class SingleLineFormatter extends Formatter {
+ private static final String SEP = System.getProperty("line.separator", "\n");
+
+ @Override
+ public synchronized String format(final LogRecord record) {
+ final boolean exception = record.getThrown() != null;
+ final StringBuilder sbuf = new StringBuilder();
+ sbuf.append(record.getLevel().getLocalizedName());
+ sbuf.append(" - ");
+ sbuf.append(this.formatMessage(record));
+ sbuf.append(SEP);
+ if (exception) {
+ try {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+ record.getThrown().printStackTrace(pw);
+ pw.close();
+ sbuf.append(sw.toString());
+ } catch (final Exception ex) {
+ // no-op
+ }
+ }
+ return sbuf.toString();
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/JULLogger.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/JULLogger.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/JULLogger.java
new file mode 100644
index 0000000..4e7e938
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/JULLogger.java
@@ -0,0 +1,179 @@
+/*
+ * 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 org.apache.tomee.jul.formatter.log;
+
+import org.apache.juli.logging.Log;
+
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+// DirectJDKLog copy since it is now package scoped
+public class JULLogger implements Log {
+ /** Alternate config reader and console format
+ */
+ private static final String SIMPLE_FMT="java.util.logging.SimpleFormatter";
+ private static final String SIMPLE_CFG="org.apache.juli.JdkLoggerConfig"; //doesn't exist
+ private static final String FORMATTER="org.apache.juli.formatter";
+
+ static {
+ if (System.getProperty("java.util.logging.config.class") == null &&
+ System.getProperty("java.util.logging.config.file") == null) {
+ // default configuration - it sucks. Let's override at least the
+ // formatter for the console
+ try {
+ Class.forName(SIMPLE_CFG).newInstance();
+ } catch(final Throwable t) {
+ // no-op
+ }
+ try {
+ final Formatter fmt=(Formatter)Class.forName(System.getProperty(FORMATTER, SIMPLE_FMT)).newInstance();
+ // it is also possible that the user modified jre/lib/logging.properties -
+ // but that's really stupid in most cases
+ final Logger root=Logger.getLogger("");
+ final Handler[] handlers = root.getHandlers();
+ for( int i=0; i< handlers.length; i++ ) {
+ // I only care about console - that's what's used in default config anyway
+ if( handlers[i] instanceof ConsoleHandler) {
+ handlers[i].setFormatter(fmt);
+ }
+ }
+ } catch( Throwable t ) {
+ // no-op maybe it wasn't included - the ugly default will be used.
+ }
+ }
+ }
+
+ private final Logger logger;
+
+ public JULLogger(final String name ) {
+ logger= Logger.getLogger(name);
+ }
+
+ @Override
+ public final boolean isErrorEnabled() {
+ return logger.isLoggable(Level.SEVERE);
+ }
+
+ @Override
+ public final boolean isWarnEnabled() {
+ return logger.isLoggable(Level.WARNING);
+ }
+
+ @Override
+ public final boolean isInfoEnabled() {
+ return logger.isLoggable(Level.INFO);
+ }
+
+ @Override
+ public final boolean isDebugEnabled() {
+ return logger.isLoggable(Level.FINE);
+ }
+
+ @Override
+ public final boolean isFatalEnabled() {
+ return logger.isLoggable(Level.SEVERE);
+ }
+
+ @Override
+ public final boolean isTraceEnabled() {
+ return logger.isLoggable(Level.FINER);
+ }
+
+ @Override
+ public final void debug(final Object message) {
+ log(Level.FINE, String.valueOf(message), null);
+ }
+
+ @Override
+ public final void debug(final Object message, final Throwable t) {
+ log(Level.FINE, String.valueOf(message), t);
+ }
+
+ @Override
+ public final void trace(final Object message) {
+ log(Level.FINER, String.valueOf(message), null);
+ }
+
+ @Override
+ public final void trace(final Object message, final Throwable t) {
+ log(Level.FINER, String.valueOf(message), t);
+ }
+
+ @Override
+ public final void info(final Object message) {
+ log(Level.INFO, String.valueOf(message), null);
+ }
+
+ @Override
+ public final void info(final Object message, final Throwable t) {
+ log(Level.INFO, String.valueOf(message), t);
+ }
+
+ @Override
+ public final void warn(final Object message) {
+ log(Level.WARNING, String.valueOf(message), null);
+ }
+
+ @Override
+ public final void warn(final Object message, final Throwable t) {
+ log(Level.WARNING, String.valueOf(message), t);
+ }
+
+ @Override
+ public final void error(final Object message) {
+ log(Level.SEVERE, String.valueOf(message), null);
+ }
+
+ @Override
+ public final void error(final Object message, final Throwable t) {
+ log(Level.SEVERE, String.valueOf(message), t);
+ }
+
+ @Override
+ public final void fatal(final Object message) {
+ log(Level.SEVERE, String.valueOf(message), null);
+ }
+
+ @Override
+ public final void fatal(final Object message, final Throwable t) {
+ log(Level.SEVERE, String.valueOf(message), t);
+ }
+
+ private void log(final Level level, final String msg, final Throwable ex) {
+ if (logger.isLoggable(level)) {
+ // Hack (?) to get the stack trace.
+ final Throwable dummyException=new Throwable();
+ final StackTraceElement[] locations=dummyException.getStackTrace();
+ // Caller will be the third element
+ String cname = "unknown";
+ String method = "unknown";
+ if (locations != null && locations.length > 3) {
+ final StackTraceElement caller = locations[3];
+ cname = caller.getClassName();
+ method = caller.getMethodName();
+ }
+ if (ex==null) {
+ logger.logp(level, cname, method, msg);
+ } else {
+ logger.logp(level, cname, method, msg, ex);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/LoggerFactory.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/LoggerFactory.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/LoggerFactory.java
new file mode 100644
index 0000000..5e9f8ce
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/LoggerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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 org.apache.tomee.jul.formatter.log;
+
+import org.apache.juli.logging.Log;
+
+public interface LoggerFactory {
+ Log newInstance(String name);
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/NoopLogger.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/NoopLogger.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/NoopLogger.java
new file mode 100644
index 0000000..a8c6a0c
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/NoopLogger.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.apache.tomee.jul.formatter.log;
+
+import org.apache.juli.logging.Log;
+
+public final class NoopLogger implements Log {
+ public static final NoopLogger INSTANCE = new NoopLogger();
+
+ private NoopLogger() {
+ // no-op
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isFatalEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return false;
+ }
+
+ @Override
+ public void trace(final Object message) {
+ // no-op
+ }
+
+ @Override
+ public void trace(final Object message, final Throwable t) {
+ // no-op
+ }
+
+ @Override
+ public void debug(final Object message) {
+ // no-op
+ }
+
+ @Override
+ public void debug(final Object message, final Throwable t) {
+ // no-op
+ }
+
+ @Override
+ public void info(final Object message) {
+ // no-op
+ }
+
+ @Override
+ public void info(final Object message, final Throwable t) {
+ // no-op
+ }
+
+ @Override
+ public void warn(final Object message) {
+ // no-op
+ }
+
+ @Override
+ public void warn(final Object message, final Throwable t) {
+ // no-op
+ }
+
+ @Override
+ public void error(final Object message) {
+ // no-op
+ }
+
+ @Override
+ public void error(final Object message, final Throwable t) {
+ // no-op
+ }
+
+ @Override
+ public void fatal(final Object message) {
+ // no-op
+ }
+
+ @Override
+ public void fatal(final Object message, final Throwable t) {
+ // no-op
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/ReloadableLog.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/ReloadableLog.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/ReloadableLog.java
new file mode 100644
index 0000000..9ca1061
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/ReloadableLog.java
@@ -0,0 +1,111 @@
+/*
+ * 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 org.apache.tomee.jul.formatter.log;
+
+import org.apache.juli.logging.Log;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class ReloadableLog {
+
+ public static final Class<?>[] INTERFACES = new Class<?>[]{Log.class};
+
+ private ReloadableLog() {
+ // no-op
+ }
+
+ public static Log newLog(final String name, final String factory) {
+ return Log.class.cast(Proxy.newProxyInstance(
+ ReloadableLog.class.getClassLoader(), INTERFACES, new ReloadableLogHandler(factory, name)));
+ }
+
+ private static final class ReloadableLogHandler implements InvocationHandler {
+ private static final String LOG4J_IMPL = "org.apache.tomee.loader.log.Log4jLog";
+ private static final String LOG4J2_IMPL = "org.apache.tomee.loader.log.Log4j2Log";
+ private static final String SLF4J_IMPL = "org.apache.tomee.loader.log.Slf4jLog";
+ private static final String MAVEN_IMPL = "org.apache.openejb.maven.util.TomEEMavenLog";
+
+ private volatile String factory;
+ private final String name;
+ private final AtomicReference<Log> delegate = new AtomicReference<Log>();
+ private volatile boolean done = false;
+
+ public ReloadableLogHandler(final String factory, final String name) {
+ this.factory = factory;
+ this.name = name;
+ initDelegate();
+ }
+
+ private Log initDelegate() {
+ if (done) {
+ return delegate.get();
+ }
+
+ try {
+ if (factory == null) {
+ final String f = TomEELog.getLoggerClazz();
+ if (f != null) {
+ factory = f;
+ }
+
+ final Log log = delegate.get();
+ if (factory == null && log != null) {
+ return log;
+ }
+ }
+ if ("org.apache.openejb.util.Log4jLogStreamFactory".equals(factory)) {
+ delegate.set(newInstance(LOG4J_IMPL));
+ } else if ("org.apache.openejb.util.Log4j2LogStreamFactory".equals(factory)) {
+ delegate.set(newInstance(LOG4J2_IMPL));
+ } else if ("org.apache.openejb.util.Slf4jLogStreamFactory".equals(factory)) {
+ delegate.set(newInstance(SLF4J_IMPL));
+ } else if ("org.apache.openejb.maven.util.MavenLogStreamFactory".equals(factory)) {
+ delegate.set(newInstance(MAVEN_IMPL));
+ } else {
+ delegate.set(new JULLogger(name));
+ }
+ done = true;
+ } catch (final Throwable the) {
+ if (delegate.get() == null) {
+ delegate.set(new JULLogger(name));
+ }
+ }
+ return delegate.get();
+ }
+
+ private Log newInstance(final String impl) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
+ return Log.class.cast(Thread.currentThread()
+ .getContextClassLoader()
+ .loadClass(impl)
+ .getConstructor(String.class)
+ .newInstance(name));
+ }
+
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ try {
+ return method.invoke(initDelegate(), args);
+ } catch (final InvocationTargetException ite) {
+ throw ite.getCause();
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/TomEELog.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/TomEELog.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/TomEELog.java
new file mode 100644
index 0000000..422317c
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/formatter/log/TomEELog.java
@@ -0,0 +1,164 @@
+/*
+ * 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 org.apache.tomee.jul.formatter.log;
+
+import org.apache.juli.logging.Log;
+
+import java.lang.reflect.Method;
+
+public class TomEELog implements Log {
+ private static volatile boolean initialized;
+ private static volatile String loggerClazz;
+ private static volatile boolean defaultLogger;
+
+ private static synchronized void initialize() {
+ if (initialized) {
+ return;
+ }
+
+ if (!Boolean.getBoolean("tomee.skip-tomcat-log")) {
+ final Thread thread = Thread.currentThread();
+ try {
+ final ClassLoader tccl = thread.getContextClassLoader();
+ final Class<?> systemInstance = tccl.loadClass("org.apache.openejb.loader.SystemInstance");
+ if (!Boolean.class.cast(systemInstance.getMethod("isInitialized").invoke(null))) {
+ return;
+ }
+
+ final Class<?> logger = tccl.loadClass("org.apache.openejb.util.Logger");
+ final Method m = logger.getDeclaredMethod("delegateClass");
+ loggerClazz = (String) m.invoke(null);
+ if ("org.apache.openejb.util.Log4j2LogStreamFactory".equals(loggerClazz)
+ || "org.apache.openejb.util.Log4jLogStreamFactory".equals(loggerClazz)
+ || "org.apache.openejb.util.Slf4jLogStreamFactory".equals(loggerClazz)
+ || "org.apache.openejb.maven.util.MavenLogStreamFactory".equals(loggerClazz)) {
+ defaultLogger = false;
+ } else {
+ defaultLogger = false;
+ }
+ initialized = true;
+ } catch (final Throwable th) {
+ // no-op
+ }
+ }
+ }
+
+ public static String getLoggerClazz() {
+ return loggerClazz;
+ }
+
+ private final Log delegate;
+
+ public TomEELog() { // for ServiceLoader
+ delegate = null;
+ }
+
+ public TomEELog(final String name) {
+ initialize();
+ this.delegate = defaultLogger ? new JULLogger(name) : ReloadableLog.newLog(name, loggerClazz);
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return delegate.isDebugEnabled();
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return delegate.isErrorEnabled();
+ }
+
+ @Override
+ public boolean isFatalEnabled() {
+ return delegate.isFatalEnabled();
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return delegate.isInfoEnabled();
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return delegate.isTraceEnabled();
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return delegate.isWarnEnabled();
+ }
+
+ @Override
+ public void trace(final Object message) {
+ delegate.trace(message);
+ }
+
+ @Override
+ public void trace(final Object message, final Throwable t) {
+ delegate.trace(message, t);
+ }
+
+ @Override
+ public void debug(final Object message) {
+ delegate.debug(message);
+ }
+
+ @Override
+ public void debug(final Object message, final Throwable t) {
+ delegate.debug(message, t);
+ }
+
+ @Override
+ public void info(final Object message) {
+ delegate.info(message);
+ }
+
+ @Override
+ public void info(final Object message, final Throwable t) {
+ delegate.info(message, t);
+ }
+
+ @Override
+ public void warn(final Object message) {
+ delegate.warn(message);
+ }
+
+ @Override
+ public void warn(final Object message, final Throwable t) {
+ delegate.warn(message, t);
+ }
+
+ @Override
+ public void error(final Object message) {
+ delegate.error(message);
+ }
+
+ @Override
+ public void error(final Object message, final Throwable t) {
+ delegate.error(message, t);
+ }
+
+ @Override
+ public void fatal(final Object message) {
+ delegate.fatal(message);
+ }
+
+ @Override
+ public void fatal(final Object message, final Throwable t) {
+ delegate.fatal(message, t);
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/BackgroundTaskRunner.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/BackgroundTaskRunner.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/BackgroundTaskRunner.java
new file mode 100644
index 0000000..5b88b5f
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/BackgroundTaskRunner.java
@@ -0,0 +1,91 @@
+/*
+ * 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 org.apache.tomee.jul.handler.rotating;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.LogManager;
+
+// Note: don't touch this class while not needed to avoid to trigger the executor service init
+// mainly there to avoid all handlers to have their own threads
+class BackgroundTaskRunner {
+ private static final ExecutorService EXECUTOR_SERVICE;
+ private static final boolean SYNCHRONOUS;
+
+ static {
+ final LogManager logManager = LogManager.getLogManager();
+ SYNCHRONOUS = Boolean.parseBoolean(getProperty(logManager, BackgroundTaskRunner.class.getName() + ".synchronous"));
+ if (SYNCHRONOUS) {
+ EXECUTOR_SERVICE = null;
+ } else {
+
+ final String threadCount = getProperty(logManager, BackgroundTaskRunner.class.getName() + ".threads");
+ final String shutdownTimeoutStr = getProperty(logManager, BackgroundTaskRunner.class.getName() + ".shutdownTimeout");
+ final Duration shutdownTimeout = new Duration(shutdownTimeoutStr == null ? "30 seconds" : shutdownTimeoutStr);
+ EXECUTOR_SERVICE = Executors.newFixedThreadPool(Integer.parseInt(threadCount == null ? "2" : threadCount), new ThreadFactory() {
+ private final ThreadGroup group;
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ private final String namePrefix = "com.tomitribe.logging.jul.handler.BackgroundTaskThread-";
+
+ {
+ SecurityManager s = System.getSecurityManager();
+ group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ }
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
+ if (!t.isDaemon()) {
+ t.setDaemon(true);
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+ });
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ EXECUTOR_SERVICE.shutdown();
+ try {
+ EXECUTOR_SERVICE.awaitTermination(shutdownTimeout.asMillis(), TimeUnit.MILLISECONDS);
+ } catch (final InterruptedException e) {
+ Thread.interrupted();
+ }
+ }
+ });
+ }
+ }
+
+ private static String getProperty(final LogManager logManager, final String key) {
+ final String val = logManager.getProperty(key);
+ return val != null ? val : System.getProperty(key);
+ }
+
+ static void push(final Runnable runnable) {
+ if (SYNCHRONOUS) {
+ runnable.run();
+ } else {
+ EXECUTOR_SERVICE.submit(runnable);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Duration.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Duration.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Duration.java
new file mode 100644
index 0000000..005f43a
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Duration.java
@@ -0,0 +1,178 @@
+/*
+ * 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 org.apache.tomee.jul.handler.rotating;
+
+import java.util.concurrent.TimeUnit;
+
+class Duration {
+ private long time;
+ private TimeUnit unit = TimeUnit.MILLISECONDS;
+
+ private Duration() {
+ // no-op
+ }
+
+ private Duration(final long time, final TimeUnit unit) {
+ this.time = time;
+ this.unit = unit;
+ }
+
+ Duration(final String string) {
+ this(string, null);
+ }
+
+ private Duration(final String string, final TimeUnit defaultUnit) {
+ final String[] strings = string.split(",| and ");
+
+ Duration total = new Duration();
+
+ for (final String value : strings) {
+ final Duration part = new Duration();
+ final String s = value.trim();
+
+ final StringBuilder t = new StringBuilder();
+ final StringBuilder u = new StringBuilder();
+
+ int i = 0;
+
+ // get the number
+ for (; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (Character.isDigit(c) || i == 0 && c == '-') {
+ t.append(c);
+ } else {
+ break;
+ }
+ }
+
+ if (t.length() == 0) {
+ invalidFormat(s);
+ }
+
+ // skip whitespace
+ for (; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+
+ // get time unit text part
+ for (; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (Character.isLetter(c)) {
+ u.append(c);
+ } else {
+ invalidFormat(s);
+ }
+ }
+
+ part.time = Long.parseLong(t.toString());
+
+ part.unit = parseUnit(u.toString());
+
+ if (part.unit == null) {
+ part.unit = defaultUnit;
+ }
+
+ total = total.add(part);
+ }
+
+ this.time = total.time;
+ this.unit = total.unit;
+ }
+
+ public long asMillis() {
+ return unit.toMillis(time);
+ }
+
+ private static class Normalize {
+ private long a;
+ private long b;
+ private TimeUnit base;
+
+ private Normalize(final Duration a, final Duration b) {
+ this.base = lowest(a, b);
+ this.a = a.unit == null ? a.time : base.convert(a.time, a.unit);
+ this.b = b.unit == null ? b.time : base.convert(b.time, b.unit);
+ }
+
+ private static TimeUnit lowest(final Duration a, final Duration b) {
+ if (a.unit == null) return b.unit;
+ if (b.unit == null) return a.unit;
+ if (a.time == 0) return b.unit;
+ if (b.time == 0) return a.unit;
+ return TimeUnit.values()[Math.min(a.unit.ordinal(), b.unit.ordinal())];
+ }
+ }
+
+ public Duration add(final Duration that) {
+ final Normalize n = new Normalize(this, that);
+ return new Duration(n.a + n.b, n.base);
+ }
+
+ private static void invalidFormat(final String text) {
+ throw new IllegalArgumentException("Illegal duration format: '" + text +
+ "'. Valid examples are '10s' or '10 seconds'.");
+ }
+
+ private static TimeUnit parseUnit(final String u) {
+ if (u.length() == 0) {
+ return null;
+ }
+
+ if ("NANOSECONDS".equalsIgnoreCase(u)) return TimeUnit.NANOSECONDS;
+ if ("NANOSECOND".equalsIgnoreCase(u)) return TimeUnit.NANOSECONDS;
+ if ("NANOS".equalsIgnoreCase(u)) return TimeUnit.NANOSECONDS;
+ if ("NANO".equalsIgnoreCase(u)) return TimeUnit.NANOSECONDS;
+ if ("NS".equalsIgnoreCase(u)) return TimeUnit.NANOSECONDS;
+
+ if ("MICROSECONDS".equalsIgnoreCase(u)) return TimeUnit.MICROSECONDS;
+ if ("MICROSECOND".equalsIgnoreCase(u)) return TimeUnit.MICROSECONDS;
+ if ("MICROS".equalsIgnoreCase(u)) return TimeUnit.MICROSECONDS;
+ if ("MICRO".equalsIgnoreCase(u)) return TimeUnit.MICROSECONDS;
+
+ if ("MILLISECONDS".equalsIgnoreCase(u)) return TimeUnit.MILLISECONDS;
+ if ("MILLISECOND".equalsIgnoreCase(u)) return TimeUnit.MILLISECONDS;
+ if ("MILLIS".equalsIgnoreCase(u)) return TimeUnit.MILLISECONDS;
+ if ("MILLI".equalsIgnoreCase(u)) return TimeUnit.MILLISECONDS;
+ if ("MS".equalsIgnoreCase(u)) return TimeUnit.MILLISECONDS;
+
+ if ("SECONDS".equalsIgnoreCase(u)) return TimeUnit.SECONDS;
+ if ("SECOND".equalsIgnoreCase(u)) return TimeUnit.SECONDS;
+ if ("SEC".equalsIgnoreCase(u)) return TimeUnit.SECONDS;
+ if ("S".equalsIgnoreCase(u)) return TimeUnit.SECONDS;
+
+ if ("MINUTES".equalsIgnoreCase(u)) return TimeUnit.MINUTES;
+ if ("MINUTE".equalsIgnoreCase(u)) return TimeUnit.MINUTES;
+ if ("MIN".equalsIgnoreCase(u)) return TimeUnit.MINUTES;
+ if ("M".equalsIgnoreCase(u)) return TimeUnit.MINUTES;
+
+ if ("HOURS".equalsIgnoreCase(u)) return TimeUnit.HOURS;
+ if ("HOUR".equalsIgnoreCase(u)) return TimeUnit.HOURS;
+ if ("HRS".equalsIgnoreCase(u)) return TimeUnit.HOURS;
+ if ("HR".equalsIgnoreCase(u)) return TimeUnit.HOURS;
+ if ("H".equalsIgnoreCase(u)) return TimeUnit.HOURS;
+
+ if ("DAYS".equalsIgnoreCase(u)) return TimeUnit.DAYS;
+ if ("DAY".equalsIgnoreCase(u)) return TimeUnit.DAYS;
+ if ("D".equalsIgnoreCase(u)) return TimeUnit.DAYS;
+
+ throw new IllegalArgumentException("Unknown time unit '" + u + "'");
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/LocalFileHandler.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/LocalFileHandler.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/LocalFileHandler.java
new file mode 100644
index 0000000..e4217ad
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/LocalFileHandler.java
@@ -0,0 +1,593 @@
+/*
+ * 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 org.apache.tomee.jul.handler.rotating;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.sql.Timestamp;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.ErrorManager;
+import java.util.logging.Filter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+import java.util.regex.Pattern;
+import java.util.zip.Deflater;
+import java.util.zip.GZIPOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * NOTE: for simplicity the prefix `org.apache.tomee.jul.handler.rotating.LocalFileHandler.` has been removed of name columns.
+ *
+ * |===
+ * | Name | Default Value | Description
+ * | filenamePattern | ${catalina.base}/logs/logs.%s.%03d.log | where log files are created, it uses String.format() and gives you the date and file number - in this order.
+ * | limit | 10 Megabytes | limit size indicating the file should be rotated
+ * | dateCheckInterval | 5 seconds | how often the date should be computed to rotate the file (don't do it each time for performances reason, means you can get few records of next day in a file name with current day)
+ * | bufferSize | -1 bytes | if positive the in memory buffer used to store data before flushing them to the disk
+ * | encoding | - | file encoding
+ * | level | ALL | level this handler accepts
+ * | filter | - | filter used to check if the message should be logged
+ * | formatter | java.util.logging.SimpleFormatter | formatter used to format messages
+ * | archiveDirectory | ${catalina.base}/logs/archives/ | where compressed logs are put.
+ * | archiveFormat | gzip | zip or gzip.
+ * | archiveOlderThan | -1 days | how many days files are kept before being compressed
+ * | purgeOlderThan | -1 days | how many days files are kept before being deleted, note: it applies on archives and not log files so 2 days of archiving and 3 days of purge makes it deleted after 5 days.
+ * | compressionLevel | -1 | In case of zip archiving the zip compression level (-1 for off or 0-9).
+ * |===
+ *
+ * NOTE: archiving and purging are done only when a file is rotated, it means it can be ignored during days if there is no logging activity.
+ *
+ * NOTE: archiving and purging is done in a background thread pool, you can configure the number of threads in thanks to
+ * `org.apache.tomee.jul.handler.rotating.BackgroundTaskRunner.threads` property in `conf/logging.properties`.
+ * Default is 2 which should be fine for most applications.
+ */
+/*
+ Open point/enhancements:
+ - date pattern/filename pattern instead of hardcoded String.format?
+ - write another async version? ensure it flushed well, use disruptor? -> bench seems to show it is useless
+ */
+public class LocalFileHandler extends Handler {
+ private static final int BUFFER_SIZE = 8102;
+
+ private long limit = 0;
+ private int bufferSize = -1;
+ private Pattern filenameRegex;
+ private Pattern archiveFilenameRegex;
+ private String filenamePattern = "${catalina.base}/logs/logs.%s.%03d.log";
+ private String archiveFormat = "gzip";
+ private long dateCheckInterval = TimeUnit.SECONDS.toMillis(5);
+ private long archiveExpiryDuration;
+ private int compressionLevel;
+ private long purgeExpiryDuration;
+ private File archiveDir;
+
+ private volatile int currentIndex;
+ private volatile long lastTimestamp;
+ private volatile String date;
+ private volatile PrintWriter writer;
+ private volatile int written;
+ private final ReadWriteLock writerLock = new ReentrantReadWriteLock();
+ private final Lock backgroundTaskLock = new ReentrantLock();
+ private volatile boolean closed;
+
+ public LocalFileHandler() {
+ configure();
+ }
+
+ private void configure() {
+ date = currentDate();
+
+ final String className = getClass().getName(); //allow classes to override
+
+ final ClassLoader cl = Thread.currentThread().getContextClassLoader();
+
+ dateCheckInterval = new Duration(getProperty(className + ".dateCheckInterval", String.valueOf(dateCheckInterval))).asMillis();
+ filenamePattern = replace(getProperty(className + ".filenamePattern", filenamePattern));
+ limit = new Size(getProperty(className + ".limit", String.valueOf("10 Mega"))).asBytes();
+
+ final int lastSep = Math.max(filenamePattern.lastIndexOf('/'), filenamePattern.lastIndexOf('\\'));
+ String fileNameReg = lastSep >= 0 ? filenamePattern.substring(lastSep + 1) : filenamePattern;
+ fileNameReg = fileNameReg.replace("%s", "\\d{4}\\-\\d{2}\\-\\d{2}"); // date.
+ { // file rotation index
+ final int indexIdxStart = fileNameReg.indexOf('%');
+ if (indexIdxStart >= 0) {
+ final int indexIdxEnd = fileNameReg.indexOf('d', indexIdxStart);
+ if (indexIdxEnd >= 0) {
+ fileNameReg = fileNameReg.substring(0, indexIdxStart) + "\\d*" + fileNameReg.substring(indexIdxEnd + 1, fileNameReg.length());
+ }
+ }
+ }
+ filenameRegex = Pattern.compile(fileNameReg);
+
+ compressionLevel = Integer.parseInt(getProperty(className + ".compressionLevel", String.valueOf(Deflater.DEFAULT_COMPRESSION)));
+ archiveExpiryDuration = new Duration(getProperty(className + ".archiveOlderThan", String.valueOf("-1 days"))).asMillis();
+ archiveDir = new File(replace(getProperty(className + ".archiveDirectory", "${catalina.base}/logs/archives/")));
+ archiveFormat = replace(getProperty(className + ".archiveFormat", archiveFormat));
+ archiveFilenameRegex = Pattern.compile(fileNameReg + "\\." + archiveFormat);
+
+ purgeExpiryDuration = new Duration(getProperty(className + ".purgeOlderThan", String.valueOf("-1 days"))).asMillis();
+
+ try {
+ bufferSize = (int) new Size(getProperty(className + ".bufferSize", "-1 b")).asBytes();
+ } catch (final NumberFormatException ignore) {
+ // no-op
+ }
+
+ final String encoding = getProperty(className + ".encoding", null);
+ if (encoding != null && encoding.length() > 0) {
+ try {
+ setEncoding(encoding);
+ } catch (final UnsupportedEncodingException ex) {
+ // no-op
+ }
+ }
+
+ setLevel(Level.parse(getProperty(className + ".level", "" + Level.ALL)));
+
+ final String filterName = getProperty(className + ".filter", null);
+ if (filterName != null) {
+ try {
+ setFilter(Filter.class.cast(cl.loadClass(filterName).newInstance()));
+ } catch (final Exception e) {
+ // Ignore
+ }
+ }
+
+ final String formatterName = getProperty(className + ".formatter", null);
+ if (formatterName != null) {
+ try {
+ setFormatter(Formatter.class.cast(cl.loadClass(formatterName).newInstance()));
+ } catch (final Exception e) {
+ setFormatter(new SimpleFormatter());
+ }
+ } else {
+ setFormatter(new SimpleFormatter());
+ }
+
+ setErrorManager(new ErrorManager());
+
+ lastTimestamp = System.currentTimeMillis();
+ }
+
+ protected String currentDate() {
+ return new Timestamp(System.currentTimeMillis()).toString().substring(0, 10);
+ }
+
+ @Override
+ public void publish(final LogRecord record) {
+ if (!isLoggable(record)) {
+ return;
+ }
+
+ final long now = System.currentTimeMillis();
+ final String tsDate;
+ // just do it once / sec if we have a lot of log, can make some log appearing in the wrong file but better than doing it each time
+ if (now - lastTimestamp > dateCheckInterval) { // using as much as possible volatile to avoid to lock too much
+ lastTimestamp = now;
+ tsDate = currentDate();
+ } else {
+ tsDate = null;
+ }
+
+ try {
+ writerLock.readLock().lock();
+ rotateIfNeeded(tsDate);
+
+ String result;
+ try {
+ result = getFormatter().format(record);
+ } catch (final Exception e) {
+ reportError(null, e, ErrorManager.FORMAT_FAILURE);
+ return;
+ }
+
+ try {
+ if (writer != null) {
+ writer.write(result);
+ if (bufferSize < 0) {
+ writer.flush();
+ }
+ } else {
+ reportError(getClass().getSimpleName() + " is closed or not yet initialized, unable to log [" + result + "]", null, ErrorManager.WRITE_FAILURE);
+ }
+ } catch (final Exception e) {
+ reportError(null, e, ErrorManager.WRITE_FAILURE);
+ }
+ } finally {
+ writerLock.readLock().unlock();
+ }
+ }
+
+ private void rotateIfNeeded(final String currentDate) {
+ if (!closed && writer == null) {
+ try {
+ writerLock.readLock().unlock();
+ writerLock.writeLock().lock();
+
+ if (!closed && writer == null) {
+ openWriter();
+ }
+ } finally {
+ writerLock.writeLock().unlock();
+ writerLock.readLock().lock();
+ }
+ } else if (shouldRotate(currentDate)) {
+ try {
+ writerLock.readLock().unlock();
+ writerLock.writeLock().lock();
+
+ if (shouldRotate(currentDate)) {
+ close();
+ if (currentDate != null && !date.equals(currentDate)) {
+ currentIndex = 0;
+ date = currentDate;
+ }
+ openWriter();
+ }
+ } finally {
+ writerLock.writeLock().unlock();
+ writerLock.readLock().lock();
+ }
+ }
+ }
+
+ private boolean shouldRotate(final String currentDate) { // new day, new file or limit exceeded
+ return (currentDate != null && !date.equals(currentDate)) || (limit > 0 && written >= limit);
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+
+ writerLock.writeLock().lock();
+ try {
+ if (writer == null) {
+ return;
+ }
+ writer.write(getFormatter().getTail(this));
+ writer.flush();
+ writer.close();
+ writer = null;
+ } catch (final Exception e) {
+ reportError(null, e, ErrorManager.CLOSE_FAILURE);
+ } finally {
+ writerLock.writeLock().unlock();
+ }
+
+ // wait for bg tasks if running
+ backgroundTaskLock.lock();
+ backgroundTaskLock.unlock();
+ }
+
+ @Override
+ public void flush() {
+ writerLock.readLock().lock();
+ try {
+ writer.flush();
+ } catch (final Exception e) {
+ reportError(null, e, ErrorManager.FLUSH_FAILURE);
+ } finally {
+ writerLock.readLock().unlock();
+ }
+ }
+
+ protected void openWriter() {
+ final long beforeRotation = System.currentTimeMillis();
+
+ writerLock.writeLock().lock();
+ FileOutputStream fos = null;
+ OutputStream os = null;
+ try {
+ File pathname;
+ do {
+ pathname = new File(formatFilename(filenamePattern, date, currentIndex));
+ final File parent = pathname.getParentFile();
+ if (!parent.isDirectory() && !parent.mkdirs()) {
+ reportError("Unable to create [" + parent + "]", null, ErrorManager.OPEN_FAILURE);
+ writer = null;
+ return;
+ }
+ currentIndex++;
+ } while (pathname.isFile()); // loop to ensure we don't overwrite existing files
+
+ final String encoding = getEncoding();
+ fos = new FileOutputStream(pathname, true);
+ os = new CountingStream(bufferSize > 0 ? new BufferedOutputStream(fos, bufferSize) : fos);
+ writer = new PrintWriter((encoding != null) ? new OutputStreamWriter(os, encoding) : new OutputStreamWriter(os), false);
+ writer.write(getFormatter().getHead(this));
+ } catch (final Exception e) {
+ reportError(null, e, ErrorManager.OPEN_FAILURE);
+ writer = null;
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (final IOException e1) {
+ // no-op
+ }
+ }
+ if (os != null) {
+ try {
+ os.close();
+ } catch (final IOException e1) {
+ // no-op
+ }
+ }
+ } finally {
+ writerLock.writeLock().unlock();
+ }
+
+ BackgroundTaskRunner.push(new Runnable() {
+ @Override
+ public void run() {
+ backgroundTaskLock.lock();
+ try {
+ evict(beforeRotation);
+ } catch (final Exception e) {
+ reportError("Can't do the log eviction", e, ErrorManager.GENERIC_FAILURE);
+ } finally {
+ backgroundTaskLock.unlock();
+ }
+ }
+ });
+ }
+
+ private void evict(final long now) {
+ if (purgeExpiryDuration > 0) { // purging archives
+ final File[] archives = archiveDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(final File dir, final String name) {
+ return archiveFilenameRegex.matcher(name).matches();
+ }
+ });
+
+ if (archives != null) {
+ for (final File archive : archives) {
+ try {
+ final BasicFileAttributes attr = Files.readAttributes(archive.toPath(), BasicFileAttributes.class);
+ if (now - attr.creationTime().toMillis() > purgeExpiryDuration) {
+ if (!Files.deleteIfExists(archive.toPath())) {
+ // dont try to delete on exit cause we will find it again
+ reportError("Can't delete " + archive.getAbsolutePath() + ".", null, ErrorManager.GENERIC_FAILURE);
+ }
+ }
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ }
+ if (archiveExpiryDuration > 0) { // archiving log files
+ final File[] logs = new File(formatFilename(filenamePattern, "0000-00-00", 0)).getParentFile()
+ .listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(final File dir, final String name) {
+ return filenameRegex.matcher(name).matches();
+ }
+ });
+
+ if (logs != null) {
+ for (final File file : logs) {
+ try {
+ final BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
+ if (attr.creationTime().toMillis() < now && now - attr.lastModifiedTime().toMillis() > archiveExpiryDuration) {
+ createArchive(file);
+ }
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ }
+ }
+
+ private String formatFilename(final String pattern, final String date, final int index) {
+ return String.format(pattern, date, index);
+ }
+
+ private void createArchive(final File source) {
+ final File target = new File(archiveDir, source.getName() + "." + archiveFormat);
+ if (target.isFile()) {
+ return;
+ }
+
+ final File parentFile = target.getParentFile();
+ if (!parentFile.isDirectory() && !parentFile.mkdirs()) {
+ throw new IllegalStateException("Can't create " + parentFile.getAbsolutePath());
+ }
+
+ if (archiveFormat.equalsIgnoreCase("gzip")) {
+ OutputStream outputStream = null;
+ try {
+ outputStream = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(target)));
+ final byte[] buffer = new byte[BUFFER_SIZE];
+ FileInputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(source);
+ copyStream(inputStream, outputStream, buffer);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException ioe) {
+ // no-op
+ }
+ }
+ }
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (final IOException ioe) {
+ // no-op
+ }
+ }
+ }
+ } else { // consider file defines a zip whatever extension it is
+ ZipOutputStream outputStream = null;
+ try {
+ outputStream = new ZipOutputStream(new FileOutputStream(target));
+ outputStream.setLevel(compressionLevel);
+
+ final byte[] buffer = new byte[BUFFER_SIZE];
+ FileInputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(source);
+ final ZipEntry zipEntry = new ZipEntry(source.getName());
+ outputStream.putNextEntry(zipEntry);
+ copyStream(inputStream, outputStream, buffer);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException ioe) {
+ // no-op
+ }
+ }
+ }
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (final IOException ioe) {
+ // no-op
+ }
+ }
+ }
+ }
+ try {
+ if (!Files.deleteIfExists(source.toPath())) {
+ reportError("Can't delete " + source.getAbsolutePath() + ".", null, ErrorManager.GENERIC_FAILURE);
+ }
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void copyStream(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) throws IOException {
+ int n;
+ while ((n = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, n);
+ }
+ }
+
+ protected String getProperty(final String name, final String defaultValue) {
+ String value = LogManager.getLogManager().getProperty(name);
+ if (value == null) {
+ value = defaultValue;
+ } else {
+ value = value.trim();
+ }
+ return value;
+ }
+
+ protected static String replace(final String str) { // [lang3] would be good but no dep for these classes is better
+ String result = str;
+ int start = str.indexOf("${");
+ if (start >= 0) {
+ final StringBuilder builder = new StringBuilder();
+ int end = -1;
+ while (start >= 0) {
+ builder.append(str, end + 1, start);
+ end = str.indexOf('}', start + 2);
+ if (end < 0) {
+ end = start - 1;
+ break;
+ }
+
+ final String propName = str.substring(start + 2, end);
+ String replacement = !propName.isEmpty() ? System.getProperty(propName) : null;
+ if (replacement == null) {
+ replacement = System.getenv(propName);
+ }
+ if (replacement != null) {
+ builder.append(replacement);
+ } else {
+ builder.append(str, start, end + 1);
+ }
+ start = str.indexOf("${", end + 1);
+ }
+ builder.append(str, end + 1, str.length());
+ result = builder.toString();
+ }
+ return result;
+ }
+
+ private class CountingStream extends OutputStream {
+ private final OutputStream out;
+
+ private CountingStream(final OutputStream out) {
+ this.out = out;
+ written = 0;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ out.write(b);
+ written++;
+ }
+
+ @Override
+ public void write(final byte buff[]) throws IOException {
+ out.write(buff);
+ written += buff.length;
+ }
+
+ @Override
+ public void write(final byte buff[], final int off, final int len) throws IOException {
+ out.write(buff, off, len);
+ written += len;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ out.close();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/da4df975/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Size.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Size.java b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Size.java
new file mode 100644
index 0000000..be1b476
--- /dev/null
+++ b/tomee/tomee-juli/src/main/java/org/apache/tomee/jul/handler/rotating/Size.java
@@ -0,0 +1,343 @@
+/*
+ * 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 org.apache.tomee.jul.handler.rotating;
+
+import static java.lang.Long.MAX_VALUE;
+
+class Size {
+ private long size;
+ private SizeUnit unit;
+
+ private Size() {
+ // no-op
+ }
+
+ private Size(final long size, final SizeUnit unit) {
+ this.size = size;
+ this.unit = unit;
+ }
+
+ Size(final String string) {
+ this(string, null);
+ }
+
+ private Size(final String string, final SizeUnit defaultUnit) {
+ final String[] strings = string.split(",| and ");
+
+ Size total = new Size();
+ for (String s : strings) {
+ final Size part = new Size();
+ s = s.trim();
+
+ final StringBuilder t = new StringBuilder();
+ final StringBuilder u = new StringBuilder();
+
+ int i = 0;
+
+ // get the number
+ for (; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (Character.isDigit(c) || i == 0 && c == '-' || i > 0 && c == '.') {
+ t.append(c);
+ } else {
+ break;
+ }
+ }
+
+ if (t.length() == 0) {
+ invalidFormat(s);
+ }
+
+ // skip whitespace
+ for (; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ }
+
+ // get time unit text part
+ for (; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ if (Character.isLetter(c)) {
+ u.append(c);
+ } else {
+ invalidFormat(s);
+ }
+ }
+
+
+ part.unit = parseUnit(u.toString());
+
+ if (part.unit == null) {
+ part.unit = defaultUnit;
+ }
+
+ final String size = t.toString();
+ if (size.contains(".")) {
+ if (part.unit == null) {
+ throw new IllegalArgumentException("unit must be specified with floating point numbers");
+ }
+ final double d = Double.parseDouble(size);
+ final long bytes = part.unit.toBytes(1);
+ part.size = (long) (bytes * d);
+ part.unit = SizeUnit.BYTES;
+ } else {
+ part.size = Integer.parseInt(size);
+ }
+
+ total = total.add(part);
+ }
+
+ this.size = total.size;
+ this.unit = total.unit;
+ }
+
+ public long asBytes() {
+ return unit.toBytes(size);
+ }
+
+ private static class Normalize {
+ private long a;
+ private long b;
+ private SizeUnit base;
+
+ private Normalize(final Size a, final Size b) {
+ this.base = lowest(a, b);
+ this.a = a.unit == null ? a.size : base.convert(a.size, a.unit);
+ this.b = b.unit == null ? b.size : base.convert(b.size, b.unit);
+ }
+
+ private static SizeUnit lowest(final Size a, final Size b) {
+ if (a.unit == null) return b.unit;
+ if (b.unit == null) return a.unit;
+ if (a.size == 0) return b.unit;
+ if (b.size == 0) return a.unit;
+ return SizeUnit.values()[Math.min(a.unit.ordinal(), b.unit.ordinal())];
+ }
+ }
+
+ public Size add(final Size that) {
+ final Normalize n = new Normalize(this, that);
+ return new Size(n.a + n.b, n.base);
+ }
+
+ private static void invalidFormat(final String text) {
+ throw new IllegalArgumentException("Illegal size format: '" + text + "'. Valid examples are '10kb' or '10 kilobytes'.");
+ }
+
+ private static SizeUnit parseUnit(final String u) {
+ if (u.length() == 0) return null;
+
+ if ("BYTES".equalsIgnoreCase(u)) return SizeUnit.BYTES;
+ if ("BYTE".equalsIgnoreCase(u)) return SizeUnit.BYTES;
+ if ("B".equalsIgnoreCase(u)) return SizeUnit.BYTES;
+
+ if ("KILOBYTES".equalsIgnoreCase(u)) return SizeUnit.KILOBYTES;
+ if ("KILOBYTE".equalsIgnoreCase(u)) return SizeUnit.KILOBYTES;
+ if ("KILO".equalsIgnoreCase(u)) return SizeUnit.KILOBYTES;
+ if ("KB".equalsIgnoreCase(u)) return SizeUnit.KILOBYTES;
+ if ("K".equalsIgnoreCase(u)) return SizeUnit.KILOBYTES;
+
+ if ("MEGABYTES".equalsIgnoreCase(u)) return SizeUnit.MEGABYTES;
+ if ("MEGABYTE".equalsIgnoreCase(u)) return SizeUnit.MEGABYTES;
+ if ("MEGA".equalsIgnoreCase(u)) return SizeUnit.MEGABYTES;
+ if ("MB".equalsIgnoreCase(u)) return SizeUnit.MEGABYTES;
+ if ("M".equalsIgnoreCase(u)) return SizeUnit.MEGABYTES;
+
+ if ("GIGABYTES".equalsIgnoreCase(u)) return SizeUnit.GIGABYTES;
+ if ("GIGABYTE".equalsIgnoreCase(u)) return SizeUnit.GIGABYTES;
+ if ("GIGA".equalsIgnoreCase(u)) return SizeUnit.GIGABYTES;
+ if ("GB".equalsIgnoreCase(u)) return SizeUnit.GIGABYTES;
+ if ("G".equalsIgnoreCase(u)) return SizeUnit.GIGABYTES;
+
+ throw new IllegalArgumentException("Unknown size unit '" + u + "'");
+ }
+
+ private enum SizeUnit {
+ BYTES {
+ public long toBytes(final long s) {
+ return s;
+ }
+
+ public long toKilobytes(final long s) {
+ return s / (B1 / B0);
+ }
+
+ public long toMegabytes(final long s) {
+ return s / (B2 / B0);
+ }
+
+ public long toGigabytes(final long s) {
+ return s / (B3 / B0);
+ }
+
+ public long toTerabytes(final long s) {
+ return s / (B4 / B0);
+ }
+
+ public long convert(final long s, final SizeUnit u) {
+ return u.toBytes(s);
+ }
+ },
+
+ KILOBYTES {
+ public long toBytes(final long s) {
+ return x(s, B1 / B0, MAX_VALUE / (B1 / B0));
+ }
+
+ public long toKilobytes(final long s) {
+ return s;
+ }
+
+ public long toMegabytes(final long s) {
+ return s / (B2 / B1);
+ }
+
+ public long toGigabytes(final long s) {
+ return s / (B3 / B1);
+ }
+
+ public long toTerabytes(final long s) {
+ return s / (B4 / B1);
+ }
+
+ public long convert(final long s, final SizeUnit u) {
+ return u.toKilobytes(s);
+ }
+ },
+
+ MEGABYTES {
+ public long toBytes(final long s) {
+ return x(s, B2 / B0, MAX_VALUE / (B2 / B0));
+ }
+
+ public long toKilobytes(final long s) {
+ return x(s, B2 / B1, MAX_VALUE / (B2 / B1));
+ }
+
+ public long toMegabytes(final long s) {
+ return s;
+ }
+
+ public long toGigabytes(final long s) {
+ return s / (B3 / B2);
+ }
+
+ public long toTerabytes(final long s) {
+ return s / (B4 / B2);
+ }
+
+ public long convert(final long s, final SizeUnit u) {
+ return u.toMegabytes(s);
+ }
+ },
+
+ GIGABYTES {
+ public long toBytes(final long s) {
+ return x(s, B3 / B0, MAX_VALUE / (B3 / B0));
+ }
+
+ public long toKilobytes(final long s) {
+ return x(s, B3 / B1, MAX_VALUE / (B3 / B1));
+ }
+
+ public long toMegabytes(final long s) {
+ return x(s, B3 / B2, MAX_VALUE / (B3 / B2));
+ }
+
+ public long toGigabytes(final long s) {
+ return s;
+ }
+
+ public long toTerabytes(final long s) {
+ return s / (B4 / B3);
+ }
+
+ public long convert(final long s, final SizeUnit u) {
+ return u.toGigabytes(s);
+ }
+ },
+
+ TERABYTES {
+ public long toBytes(final long s) {
+ return x(s, B4 / B0, MAX_VALUE / (B4 / B0));
+ }
+
+ public long toKilobytes(final long s) {
+ return x(s, B4 / B1, MAX_VALUE / (B4 / B1));
+ }
+
+ public long toMegabytes(final long s) {
+ return x(s, B4 / B2, MAX_VALUE / (B4 / B2));
+ }
+
+ public long toGigabytes(final long s) {
+ return x(s, B4 / B3, MAX_VALUE / (B4 / B3));
+ }
+
+ public long toTerabytes(final long s) {
+ return s;
+ }
+
+ public long convert(final long s, final SizeUnit u) {
+ return u.toTerabytes(s);
+ }
+ };
+
+ static final long B0 = 1L;
+ static final long B1 = B0 * 1024L;
+ static final long B2 = B1 * 1024L;
+ static final long B3 = B2 * 1024L;
+ static final long B4 = B3 * 1024L;
+
+
+ static long x(final long d, final long m, final long over) {
+ if (d > over) {
+ return MAX_VALUE;
+ }
+ if (d < -over) {
+ return Long.MIN_VALUE;
+ }
+ return d * m;
+ }
+
+ public long toBytes(final long size) {
+ throw new AbstractMethodError();
+ }
+
+ public long toKilobytes(final long size) {
+ throw new AbstractMethodError();
+ }
+
+ public long toMegabytes(final long size) {
+ throw new AbstractMethodError();
+ }
+
+ public long toGigabytes(final long size) {
+ throw new AbstractMethodError();
+ }
+
+ public long toTerabytes(final long size) {
+ throw new AbstractMethodError();
+ }
+
+ public long convert(final long sourceSize, final SizeUnit sourceUnit) {
+ throw new AbstractMethodError();
+ }
+ }
+}