You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/06/28 15:05:08 UTC
incubator-freemarker git commit: Forward ported #attempt error
reporting improvements from 2.3.27. Furthermore,
#attempt now logs errors into the org.apache.freemarker.core.Runtime.Attempt
log chategory (by
Repository: incubator-freemarker
Updated Branches:
refs/heads/3 02aea4474 -> 2548f5cdc
Forward ported #attempt error reporting improvements from 2.3.27. Furthermore, #attempt now logs errors into the org.apache.freemarker.core.Runtime.Attempt log chategory (by
default). It doesn't create an additional debug level log entry anymore.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/2548f5cd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/2548f5cd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/2548f5cd
Branch: refs/heads/3
Commit: 2548f5cdc78fe05372ba314fc1e0415d154fd7ba
Parents: 02aea44
Author: ddekany <dd...@apache.org>
Authored: Wed Jun 28 17:04:52 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Wed Jun 28 17:04:52 2017 +0200
----------------------------------------------------------------------
FM3-CHANGE-LOG.txt | 6 +-
.../freemarker/core/AttemptLoggingTest.java | 100 +++++++++++++++++++
.../freemarker/core/ConfigurationTest.java | 32 +++++-
.../core/TemplateConfigurationTest.java | 1 +
.../core/AttemptExceptionReporter.java | 45 +++++++++
.../apache/freemarker/core/Configuration.java | 21 ++++
.../org/apache/freemarker/core/Environment.java | 35 ++++---
.../core/LoggingAttemptExceptionReporter.java | 43 ++++++++
.../core/MutableProcessingConfiguration.java | 87 +++++++++++++++-
.../core/ProcessingConfiguration.java | 26 ++++-
.../org/apache/freemarker/core/Template.java | 14 ++-
.../freemarker/core/TemplateConfiguration.java | 27 ++++-
12 files changed, 410 insertions(+), 27 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index a930bc6..ba175ee 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -264,6 +264,8 @@ the FreeMarer 3 changelog here:
"allowsNothing" were renamed to "allow_nothing" and "allowNothing".
- TemplateExceptionHandler.IGNORE_HANDLER, RETHROW_HANDLER, DEBUG_HANDLER and
HTLM_DEBUG_HANDLER was renamed to IGNORE, RETHROW, DEBUG and HTML_DEBUG
+- AttemptExceptionReporter.LOG_ERROR_REPORTER and LOG_WARN_REPORTER was renamed to
+ LOG_ERROR and LOG_WARN (to be consistent with the new TemplateExceptionHandler names)
- Renamed the `cacheStorage` Configuration setting to `templateCacheStorage`.
- Renamed the `localizedLookup` Configuration setting to `localizedLookup`.
- Changed the defaults of some Configuration settings:
@@ -285,4 +287,6 @@ the FreeMarer 3 changelog here:
get(), to be more similar to the Java 8 API.
- Removed long deprecated `#{}` interpolations. They are treated as plain static text now. (The
template converter tool translates these to `${}` interpolations. For example `#{x}` is simply
- translated to `${b}`, while `#{x; m1M3}` is translated to `${x?string('0.0##')}`).
\ No newline at end of file
+ translated to `${b}`, while `#{x; m1M3}` is translated to `${x?string('0.0##')}`).
+- #attempt now logs errors into the org.apache.freemarker.core.Runtime.Attempt log chategory (by
+ default). It doesn't create an additional debug level log entry anymore.
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core-test/src/test/java/org/apache/freemarker/core/AttemptLoggingTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/AttemptLoggingTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/AttemptLoggingTest.java
new file mode 100644
index 0000000..2b89776
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/AttemptLoggingTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.freemarker.core;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+public class AttemptLoggingTest extends TemplateTest {
+
+ @Test
+ public void standardConfigTest() throws IOException, TemplateException {
+ assertOutput("<#attempt>${missingVar1}<#recover>r</#attempt>", "r");
+ // Here, we should have an ERROR entry in the log that refers to an exception in an #attempt block. But we can't
+ // easily assert that automatically, so it has to be checked manually...
+
+ setConfiguration(new TestConfigurationBuilder()
+ .attemptExceptionReporter(AttemptExceptionReporter.LOG_WARN)
+ .build());
+ assertOutput("<#attempt>${missingVar2}<#recover>r</#attempt>", "r");
+ // Again, it must be checked manually if there's a WARN entry
+ }
+
+ @Test
+ public void customConfigTest() throws IOException, TemplateException {
+ List<String> reports = new ArrayList<String>();
+ setConfiguration(new TestConfigurationBuilder()
+ .attemptExceptionReporter(new TestAttemptExceptionReporter(reports))
+ .build());
+
+ assertOutput(
+ "<#attempt>${missingVar1}<#recover>r</#attempt>"
+ + "<#attempt>${missingVar2}<#recover>r</#attempt>",
+ "rr");
+ assertEquals(2, reports.size());
+ assertThat(reports.get(0), containsString("missingVar1"));
+ assertThat(reports.get(1), containsString("missingVar2"));
+ }
+
+ @Test
+ public void dontReportSuppressedExceptionsTest() throws IOException, TemplateException {
+ List<String> reports = new ArrayList<String>();
+ setConfiguration(new TestConfigurationBuilder()
+ .attemptExceptionReporter(new TestAttemptExceptionReporter(reports))
+ .templateExceptionHandler(new TemplateExceptionHandler() {
+ public void handleTemplateException(TemplateException te, Environment env, Writer out)
+ throws TemplateException {
+ try {
+ out.write("[E]");
+ } catch (IOException e) {
+ throw new TemplateException("Failed to write to the output", e, env);
+ }
+ }
+ })
+ .build());
+
+ assertOutput("<#attempt>${missingVar1}t<#recover>r</#attempt>", "[E]t");
+
+ assertEquals(0, reports.size());
+ }
+
+ private static final class TestAttemptExceptionReporter implements AttemptExceptionReporter {
+ private final List<String> reports;
+
+ private TestAttemptExceptionReporter(List<String> reports) {
+ this.reports = reports;
+ }
+
+ public void report(TemplateException te, Environment env) {
+ reports.add(te.getMessage());
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 913b388..8882a3c 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -611,11 +611,35 @@ public class ConfigurationTest {
@Test
public void testSetICIViaSetSettingAPI() throws ConfigurationException {
- Builder cfg = new Builder(VERSION_3_0_0);
- assertEquals(DEFAULT_INCOMPATIBLE_IMPROVEMENTS, cfg.getIncompatibleImprovements());
+ Builder cfgB = new Builder(VERSION_3_0_0);
+ assertEquals(DEFAULT_INCOMPATIBLE_IMPROVEMENTS, cfgB.getIncompatibleImprovements());
// This is the only valid value ATM:
- cfg.setSetting(INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0");
- assertEquals(VERSION_3_0_0, cfg.getIncompatibleImprovements());
+ cfgB.setSetting(INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0");
+ assertEquals(VERSION_3_0_0, cfgB.getIncompatibleImprovements());
+ }
+
+ @Test
+ public void testSetAttemptExceptionReporter() throws TemplateException {
+ Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
+ assertEquals(AttemptExceptionReporter.LOG_ERROR, cfgB.getAttemptExceptionReporter());
+ assertFalse(cfgB.isAttemptExceptionReporterSet());
+ cfgB.setSetting(MutableProcessingConfiguration.ATTEMPT_EXCEPTION_REPORTER_KEY, "log_warn");
+ assertEquals(AttemptExceptionReporter.LOG_WARN, cfgB.getAttemptExceptionReporter());
+ assertTrue(cfgB.isAttemptExceptionReporterSet());
+ cfgB.setSetting(MutableProcessingConfiguration.ATTEMPT_EXCEPTION_REPORTER_KEY, "default");
+ assertEquals(AttemptExceptionReporter.LOG_ERROR, cfgB.getAttemptExceptionReporter());
+ assertFalse(cfgB.isAttemptExceptionReporterSet());
+
+ assertEquals(AttemptExceptionReporter.LOG_ERROR,
+ new Configuration.Builder(Configuration.VERSION_3_0_0)
+ .build()
+ .getAttemptExceptionReporter());
+
+ assertEquals(AttemptExceptionReporter.LOG_WARN,
+ new Configuration.Builder(Configuration.VERSION_3_0_0)
+ .attemptExceptionReporter(AttemptExceptionReporter.LOG_WARN)
+ .build()
+ .getAttemptExceptionReporter());
}
@Test
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
index 4b6d4b3..355c6e7 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -167,6 +167,7 @@ public class TemplateConfigurationTest {
SETTING_ASSIGNMENTS.put("outputEncoding", StandardCharsets.UTF_16);
SETTING_ASSIGNMENTS.put("showErrorTips", false);
SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE);
+ SETTING_ASSIGNMENTS.put("attemptExceptionReporter", AttemptExceptionReporter.LOG_WARN);
SETTING_ASSIGNMENTS.put("timeFormat", "@HH:mm");
SETTING_ASSIGNMENTS.put("timeZone", NON_DEFAULT_TZ);
SETTING_ASSIGNMENTS.put("arithmeticEngine", ConservativeArithmeticEngine.INSTANCE);
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/AttemptExceptionReporter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/AttemptExceptionReporter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/AttemptExceptionReporter.java
new file mode 100644
index 0000000..b10df40
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/AttemptExceptionReporter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.freemarker.core;
+
+/**
+ * Used for the
+ * {@link ProcessingConfiguration#getAttemptExceptionReporter() attemptExceptionReported} configuration setting.
+ */
+public interface AttemptExceptionReporter {
+
+ /**
+ * Logs the exception into the "freemarker.runtime" log category with "error" log level. This is the default
+ * {@link AttemptExceptionReporter}. The error message will explain that the error was handled by an
+ * {@code #attempt} block.
+ */
+ AttemptExceptionReporter LOG_ERROR = new LoggingAttemptExceptionReporter(false);
+
+ /**
+ * Like {@link #LOG_ERROR}, but it logs with "warn" log level.
+ */
+ AttemptExceptionReporter LOG_WARN = new LoggingAttemptExceptionReporter(true);
+
+ /**
+ * Called to log or otherwise report the error that has occurred inside an {@code #attempt} block.
+ */
+ void report(TemplateException te, Environment env);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
index 8122e92..9f31556 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -254,6 +254,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
private final TimeZone sqlDateAndTimeTimeZone;
private final String booleanFormat;
private final TemplateExceptionHandler templateExceptionHandler;
+ private final AttemptExceptionReporter attemptExceptionReporter;
private final ArithmeticEngine arithmeticEngine;
private final ObjectWrapper objectWrapper;
private final Charset outputEncoding;
@@ -419,6 +420,7 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
sqlDateAndTimeTimeZone = builder.getSQLDateAndTimeTimeZone();
booleanFormat = builder.getBooleanFormat();
templateExceptionHandler = builder.getTemplateExceptionHandler();
+ attemptExceptionReporter = builder.getAttemptExceptionReporter();
arithmeticEngine = builder.getArithmeticEngine();
outputEncoding = builder.getOutputEncoding();
urlEscapingCharset = builder.getURLEscapingCharset();
@@ -524,6 +526,20 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
return true;
}
+ @Override
+ public AttemptExceptionReporter getAttemptExceptionReporter() {
+ return attemptExceptionReporter;
+ }
+
+ /**
+ * Always {@code true} in {@link Configuration}-s; even if this setting wasn't set in the builder, it gets a default
+ * value in the {@link Configuration}.
+ */
+ @Override
+ public boolean isAttemptExceptionReporterSet() {
+ return true;
+ }
+
private static class DefaultSoftCacheStorage extends SoftCacheStorage {
// Nothing to override
}
@@ -2673,6 +2689,11 @@ public final class Configuration implements TopLevelConfiguration, CustomStateSc
}
@Override
+ protected AttemptExceptionReporter getDefaultAttemptExceptionReporter() {
+ return AttemptExceptionReporter.LOG_ERROR;
+ }
+
+ @Override
protected ArithmeticEngine getDefaultArithmeticEngine() {
return BigDecimalArithmeticEngine.INSTANCE;
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
index e058da8..c825dc3 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
@@ -104,7 +104,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
private static final ThreadLocal<Environment> TLS_ENVIRONMENT = new ThreadLocal();
private static final Logger LOG = _CoreLogs.RUNTIME;
- private static final Logger LOG_ATTEMPT = _CoreLogs.ATTEMPT;
// Do not use this object directly; deepClone it first! DecimalFormat isn't
// thread-safe.
@@ -575,10 +574,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
out = prevOut;
}
if (thrownException != null) {
- if (LOG_ATTEMPT.isDebugEnabled()) {
- LOG_ATTEMPT.debug("Error in attempt block " +
- attemptBlock.getStartLocationQuoted(), thrownException);
- }
try {
recoveredErrorStack.add(thrownException);
visit(recoverySection);
@@ -887,19 +882,22 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
}
lastThrowable = templateException;
- // Log the exception if we are inside an #attempt block; it has to be logged, as it certainly won't bubble up
- // to the caller of FreeMarker.
- if (LOG.isErrorEnabled() && isInAttemptBlock()) {
- LOG.error("Error executing FreeMarker template", templateException);
- }
+ try {
+ // Stop exception is not passed to the handler, but
+ // explicitly rethrown.
+ if (templateException instanceof StopException) {
+ throw templateException;
+ }
- // Stop exception is not passed to the handler, but explicitly rethrown.
- if (templateException instanceof StopException) {
- throw templateException;
+ // Finally, pass the exception to the handler
+ getTemplateExceptionHandler().handleTemplateException(templateException, this, out);
+ } catch (TemplateException e) {
+ // Note that if the TemplateExceptionHandler doesn't rethrow the exception, we don't get in there.
+ if (isInAttemptBlock()) {
+ this.getAttemptExceptionReporter().report(templateException, this);
+ }
+ throw e;
}
-
- // Finally, pass the exception to the handler
- getTemplateExceptionHandler().handleTemplateException(templateException, this, out);
}
@Override
@@ -914,6 +912,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
}
@Override
+ protected AttemptExceptionReporter getDefaultAttemptExceptionReporter() {
+ return getMainTemplate().getAttemptExceptionReporter();
+ }
+
+ @Override
protected ArithmeticEngine getDefaultArithmeticEngine() {
return getMainTemplate().getArithmeticEngine();
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/LoggingAttemptExceptionReporter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/LoggingAttemptExceptionReporter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/LoggingAttemptExceptionReporter.java
new file mode 100644
index 0000000..6f120f0
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/LoggingAttemptExceptionReporter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.freemarker.core;
+
+/**
+ * Default {@link AttemptExceptionReporter} implementation, factored out from {@link AttemptExceptionReporter} so that
+ * we can have static field.
+ */
+class LoggingAttemptExceptionReporter implements AttemptExceptionReporter {
+
+ private final boolean logAsWarn;
+
+ public LoggingAttemptExceptionReporter(boolean logAsWarn) {
+ this.logAsWarn = logAsWarn;
+ }
+
+ public void report(TemplateException te, Environment env) {
+ String message = "Error executing FreeMarker template part in the #attempt block";
+ if (!logAsWarn) {
+ _CoreLogs.ATTEMPT.error(message, te);
+ } else {
+ _CoreLogs.ATTEMPT.warn(message, te);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
index efdb953..07ee06e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MutableProcessingConfiguration.java
@@ -150,7 +150,14 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
public static final String TEMPLATE_EXCEPTION_HANDLER_KEY_CAMEL_CASE = "templateExceptionHandler";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String TEMPLATE_EXCEPTION_HANDLER_KEY = TEMPLATE_EXCEPTION_HANDLER_KEY_SNAKE_CASE;
-
+
+ /** Legacy, snake case ({@code like_this}) variation of the setting name. */
+ public static final String ATTEMPT_EXCEPTION_REPORTER_KEY_SNAKE_CASE = "attempt_exception_reporter";
+ /** Modern, camel case ({@code likeThis}) variation of the setting name. */
+ public static final String ATTEMPT_EXCEPTION_REPORTER_KEY_CAMEL_CASE = "attemptExceptionReporter";
+ /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
+ public static final String ATTEMPT_EXCEPTION_REPORTER_KEY = ATTEMPT_EXCEPTION_REPORTER_KEY_SNAKE_CASE;
+
/** Legacy, snake case ({@code like_this}) variation of the setting name. */
public static final String ARITHMETIC_ENGINE_KEY_SNAKE_CASE = "arithmetic_engine";
/** Modern, camel case ({@code likeThis}) variation of the setting name. */
@@ -239,6 +246,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
// Must be sorted alphabetically!
API_BUILTIN_ENABLED_KEY_SNAKE_CASE,
ARITHMETIC_ENGINE_KEY_SNAKE_CASE,
+ ATTEMPT_EXCEPTION_REPORTER_KEY_SNAKE_CASE,
AUTO_FLUSH_KEY_SNAKE_CASE,
AUTO_IMPORT_KEY_SNAKE_CASE,
AUTO_INCLUDE_KEY_SNAKE_CASE,
@@ -265,6 +273,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
// Must be sorted alphabetically!
API_BUILTIN_ENABLED_KEY_CAMEL_CASE,
ARITHMETIC_ENGINE_KEY_CAMEL_CASE,
+ ATTEMPT_EXCEPTION_REPORTER_KEY_CAMEL_CASE,
AUTO_FLUSH_KEY_CAMEL_CASE,
AUTO_IMPORT_KEY_CAMEL_CASE,
AUTO_INCLUDE_KEY_CAMEL_CASE,
@@ -297,6 +306,7 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
private boolean sqlDateAndTimeTimeZoneSet;
private String booleanFormat;
private TemplateExceptionHandler templateExceptionHandler;
+ private AttemptExceptionReporter attemptExceptionReporter;
private ArithmeticEngine arithmeticEngine;
private Charset outputEncoding;
private boolean outputEncodingSet;
@@ -879,6 +889,47 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
}
/**
+ * Setter pair of {@link #getAttemptExceptionReporter()}
+ */
+ public void setAttemptExceptionReporter(AttemptExceptionReporter attemptExceptionReporter) {
+ _NullArgumentException.check("attemptExceptionReporter", attemptExceptionReporter);
+ this.attemptExceptionReporter = attemptExceptionReporter;
+ }
+
+ /**
+ * Fluent API equivalent of {@link #setAttemptExceptionReporter(AttemptExceptionReporter)}
+ */
+ public SelfT attemptExceptionReporter(AttemptExceptionReporter value) {
+ setAttemptExceptionReporter(value);
+ return self();
+ }
+
+ /**
+ * Resets the setting value as if it was never set (but it doesn't affect the value inherited from another
+ * {@link ProcessingConfiguration}).
+ */
+ public void unsetAttemptExceptionReporter() {
+ attemptExceptionReporter = null;
+ }
+
+ @Override
+ public AttemptExceptionReporter getAttemptExceptionReporter() {
+ return isAttemptExceptionReporterSet()
+ ? attemptExceptionReporter : getDefaultAttemptExceptionReporter();
+ }
+
+ /**
+ * Returns the value the getter method returns when the setting is not set (possibly by inheriting the setting value
+ * from another {@link ProcessingConfiguration}), or throws {@link CoreSettingValueNotSetException}.
+ */
+ protected abstract AttemptExceptionReporter getDefaultAttemptExceptionReporter();
+
+ @Override
+ public boolean isAttemptExceptionReporterSet() {
+ return attemptExceptionReporter != null;
+ }
+
+ /**
* Setter pair of {@link #getArithmeticEngine()}
*/
public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
@@ -1407,9 +1458,18 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
* If the value does not contain dot, then it must be one of these predefined values (case insensitive):
* {@code "rethrow"} (means {@link TemplateExceptionHandler#RETHROW}),
* {@code "debug"} (means {@link TemplateExceptionHandler#DEBUG}),
- * {@code "html_debug"} (means {@link TemplateExceptionHandler#HTML_DEBUG}),
- * {@code "ignore"} (means {@link TemplateExceptionHandler#IGNORE}),
+ * {@code "htmlDebug"} (means {@link TemplateExceptionHandler#HTML_DEBUG}),
+ * {@code "ignore"} (means {@link TemplateExceptionHandler#IGNORE}), or
* {@code "default"} (only allowed for {@link Configuration} instances) for the default.
+ *
+ * <li><p>{@code "attempt_exception_reporter"}:
+ * See {@link #setAttemptExceptionReporter(AttemptExceptionReporter)}.
+ * <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
+ * expression</a>.
+ * If the value does not contain dot, then it must be one of these predefined values (case insensitive):
+ * {@code "logError"} (means {@link AttemptExceptionReporter#LOG_ERROR}),
+ * {@code "logWarn"} (means {@link AttemptExceptionReporter#LOG_WARN}), or
+ * {@code "default"} (only allowed for {@link Configuration} instances) for the default value.
*
* <li><p>{@code "arithmetic_engine"}:
* See {@link #setArithmeticEngine(ArithmeticEngine)}.
@@ -1819,6 +1879,27 @@ public abstract class MutableProcessingConfiguration<SelfT extends MutableProces
setTemplateExceptionHandler((TemplateExceptionHandler) _ObjectBuilderSettingEvaluator.eval(
value, TemplateExceptionHandler.class, false, _SettingEvaluationEnvironment.getCurrent()));
}
+ } else if (ATTEMPT_EXCEPTION_REPORTER_KEY_SNAKE_CASE.equals(name)
+ || ATTEMPT_EXCEPTION_REPORTER_KEY_CAMEL_CASE.equals(name)) {
+ if (value.indexOf('.') == -1) {
+ if ("log_error".equalsIgnoreCase(value) || "logError".equals(value)) {
+ setAttemptExceptionReporter(
+ AttemptExceptionReporter.LOG_ERROR);
+ } else if ("log_warn".equalsIgnoreCase(value) || "logWarn".equals(value)) {
+ setAttemptExceptionReporter(
+ AttemptExceptionReporter.LOG_WARN);
+ } else if (DEFAULT_VALUE.equalsIgnoreCase(value)
+ && this instanceof Configuration.ExtendableBuilder) {
+ unsetAttemptExceptionReporter();
+ } else {
+ throw new InvalidSettingValueException(
+ name, value,
+ "No such predefined template exception handler name");
+ }
+ } else {
+ setTemplateExceptionHandler((TemplateExceptionHandler) _ObjectBuilderSettingEvaluator.eval(
+ value, TemplateExceptionHandler.class, false, _SettingEvaluationEnvironment.getCurrent()));
+ }
} else if (ARITHMETIC_ENGINE_KEY_SNAKE_CASE.equals(name) || ARITHMETIC_ENGINE_KEY_CAMEL_CASE.equals(name)) {
if (value.indexOf('.') == -1) {
if ("bigdecimal".equalsIgnoreCase(value)) {
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
index 324bb3d..1002c02 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -418,7 +418,10 @@ public interface ProcessingConfiguration {
* Neither is it meant to be used to roll back the printed output. These should be solved outside template
* processing when the exception raises from {@link Template#process(Object, Writer) Template.process}.
* {@link TemplateExceptionHandler} meant to be used if you want to include special content <em>in</em> the template
- * output, or if you want to suppress certain exceptions.
+ * output, or if you want to suppress certain exceptions. If you suppress an exception then it's the responsibility
+ * of the {@link TemplateExceptionHandler} to log the exception (if you want it to be logged).
+ *
+ * @see #getAttemptExceptionReporter()
*/
TemplateExceptionHandler getTemplateExceptionHandler();
@@ -430,6 +433,27 @@ public interface ProcessingConfiguration {
boolean isTemplateExceptionHandlerSet();
/**
+ * Specifies how exceptions handled (and hence suppressed) by an {@code #attempt} blocks will be logged or otherwise
+ * reported. The default value is {@link AttemptExceptionReporter#LOG_ERROR}.
+ *
+ * <p>Note that {@code #attempt} is not supposed to be a general purpose error handler mechanism, like {@code try}
+ * is in Java. It's for decreasing the impact of unexpected errors, by making it possible that only part of the
+ * page is going down, instead of the whole page. But it's still an error, something that someone should fix. So the
+ * error should be reported, not just ignored in a custom {@link AttemptExceptionReporter}-s.
+ *
+ * <p>The {@link AttemptExceptionReporter} is not invoked if the {@link TemplateExceptionHandler} has
+ * suppressed the exception.
+ */
+ AttemptExceptionReporter getAttemptExceptionReporter();
+
+ /**
+ * Tells if this setting is set directly in this object. If not, then depending on the implementing class, reading
+ * the setting mights returns a default value, or returns the value of the setting from a parent object, or throws
+ * a {@link CoreSettingValueNotSetException}.
+ */
+ boolean isAttemptExceptionReporterSet();
+
+ /**
* The arithmetic engine used to perform arithmetic operations.
* Its {@link Configuration}-level default is {@link BigDecimalArithmeticEngine#INSTANCE}.
* Note that this setting overlaps with {@link ParsingConfiguration#getArithmeticEngine()}.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
index 0126d60..43e2902 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
@@ -929,7 +929,8 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
@Override
public TemplateExceptionHandler getTemplateExceptionHandler() {
- return tCfg != null && tCfg.isTemplateExceptionHandlerSet() ? tCfg.getTemplateExceptionHandler() : cfg.getTemplateExceptionHandler();
+ return tCfg != null && tCfg.isTemplateExceptionHandlerSet() ? tCfg.getTemplateExceptionHandler()
+ : cfg.getTemplateExceptionHandler();
}
@Override
@@ -938,6 +939,17 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
}
@Override
+ public AttemptExceptionReporter getAttemptExceptionReporter() {
+ return tCfg != null && tCfg.isAttemptExceptionReporterSet() ? tCfg.getAttemptExceptionReporter()
+ : cfg.getAttemptExceptionReporter();
+ }
+
+ @Override
+ public boolean isAttemptExceptionReporterSet() {
+ return tCfg != null && tCfg.isAttemptExceptionReporterSet();
+ }
+
+ @Override
public ArithmeticEngine getArithmeticEngine() {
return tCfg != null && tCfg.isArithmeticEngineSet() ? tCfg.getArithmeticEngine() : cfg.getArithmeticEngine();
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2548f5cd/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
index 60fa9ed..17583e5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -62,6 +62,7 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
private final boolean sqlDateAndTimeTimeZoneSet;
private final String booleanFormat;
private final TemplateExceptionHandler templateExceptionHandler;
+ private final AttemptExceptionReporter attemptExceptionReporter;
private final ArithmeticEngine arithmeticEngine;
private final Charset outputEncoding;
private final boolean outputEncodingSet;
@@ -100,7 +101,10 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
sqlDateAndTimeTimeZoneSet = builder.isSQLDateAndTimeTimeZoneSet();
sqlDateAndTimeTimeZone = sqlDateAndTimeTimeZoneSet ? builder.getSQLDateAndTimeTimeZone() : null;
booleanFormat = builder.isBooleanFormatSet() ? builder.getBooleanFormat() : null;
- templateExceptionHandler = builder.isTemplateExceptionHandlerSet() ? builder.getTemplateExceptionHandler() : null;
+ templateExceptionHandler = builder.isTemplateExceptionHandlerSet() ? builder.getTemplateExceptionHandler()
+ : null;
+ attemptExceptionReporter = builder.isAttemptExceptionReporterSet() ? builder.getAttemptExceptionReporter()
+ : null;
arithmeticEngine = builder.isArithmeticEngineSet() ? builder.getArithmeticEngine() : null;
outputEncodingSet = builder.isOutputEncodingSet();
outputEncoding = outputEncodingSet ? builder.getOutputEncoding() : null;
@@ -429,6 +433,19 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
}
@Override
+ public AttemptExceptionReporter getAttemptExceptionReporter() {
+ if (!isAttemptExceptionReporterSet()) {
+ throw new CoreSettingValueNotSetException("attemptExceptionReporter");
+ }
+ return attemptExceptionReporter;
+ }
+
+ @Override
+ public boolean isAttemptExceptionReporterSet() {
+ return attemptExceptionReporter != null;
+ }
+
+ @Override
public Charset getOutputEncoding() {
if (!isOutputEncodingSet()) {
throw new CoreSettingValueNotSetException("");
@@ -676,6 +693,11 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
}
@Override
+ protected AttemptExceptionReporter getDefaultAttemptExceptionReporter() {
+ throw new CoreSettingValueNotSetException("attemptExceptionReporter");
+ }
+
+ @Override
protected ArithmeticEngine getDefaultArithmeticEngine() {
throw new CoreSettingValueNotSetException("arithmeticEngine");
}
@@ -822,6 +844,9 @@ public final class TemplateConfiguration implements ParsingAndProcessingConfigur
if (tc.isTemplateExceptionHandlerSet()) {
setTemplateExceptionHandler(tc.getTemplateExceptionHandler());
}
+ if (tc.isAttemptExceptionReporterSet()) {
+ setAttemptExceptionReporter(tc.getAttemptExceptionReporter());
+ }
if (tc.isTimeFormatSet()) {
setTimeFormat(tc.getTimeFormat());
}