Repository: tapestry-5 Updated Branches: refs/heads/master 48d1f6409 -> fa691e262
Extract ExceptionReportWriter from ExceptionReporterImpl Do a better job formatting the output, and display session attributes if present. Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/ede56bc6 Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/ede56bc6 Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/ede56bc6 Branch: refs/heads/master Commit: ede56bc6fb85694b969a1ffd348960e72a04f936 Parents: 48d1f64 Author: Howard M. Lewis Ship <hls...@apache.org> Authored: Wed Dec 31 16:45:37 2014 -0800 Committer: Howard M. Lewis Ship <hls...@apache.org> Committed: Wed Dec 31 16:45:37 2014 -0800 ---------------------------------------------------------------------- .../exceptions/ExceptionReportWriterImpl.java | 313 +++++++++++++++++++ .../exceptions/ExceptionReporterImpl.java | 251 +-------------- .../tapestry5/modules/TapestryModule.java | 15 +- .../services/ExceptionReportWriter.java | 38 +++ .../tapestry5/services/ExceptionReporter.java | 4 +- 5 files changed, 369 insertions(+), 252 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ede56bc6/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReportWriterImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReportWriterImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReportWriterImpl.java new file mode 100644 index 0000000..51909b8 --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReportWriterImpl.java @@ -0,0 +1,313 @@ +// Licensed 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.tapestry5.internal.services.exceptions; + +import org.apache.tapestry5.SymbolConstants; +import org.apache.tapestry5.func.F; +import org.apache.tapestry5.func.Flow; +import org.apache.tapestry5.func.Mapper; +import org.apache.tapestry5.func.Reducer; +import org.apache.tapestry5.internal.TapestryInternalUtils; +import org.apache.tapestry5.ioc.annotations.Inject; +import org.apache.tapestry5.ioc.annotations.Symbol; +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.InternalUtils; +import org.apache.tapestry5.ioc.services.ExceptionAnalysis; +import org.apache.tapestry5.ioc.services.ExceptionAnalyzer; +import org.apache.tapestry5.ioc.services.ExceptionInfo; +import org.apache.tapestry5.services.ExceptionReportWriter; +import org.apache.tapestry5.services.Request; +import org.apache.tapestry5.services.RequestGlobals; +import org.apache.tapestry5.services.Session; + +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class ExceptionReportWriterImpl implements ExceptionReportWriter +{ + private static final Reducer<Integer, Integer> MAX = new Reducer<Integer, Integer>() + { + @Override + public Integer reduce(Integer accumulator, Integer element) + { + return Math.max(accumulator, element); + } + }; + + private static final Mapper<String, Integer> STRING_TO_LENGTH = new Mapper<String, Integer>() + { + @Override + public Integer map(String element) + { + return element.length(); + } + }; + + private final static Mapper<ExceptionInfo, Flow<String>> EXCEPTION_INFO_TO_PROPERTY_NAMES = + new Mapper<ExceptionInfo, Flow<String>>() + { + @Override + public Flow<String> map(ExceptionInfo element) + { + return F.flow(element.getPropertyNames()); + } + }; + + /** + * A little closure that understands how to write a key/value pair represening a property. + */ + interface PropertyWriter + { + void write(String name, Object value); + } + + @Inject + private ExceptionAnalyzer analyzer; + + @Inject + private RequestGlobals requestGlobals; + + @Inject + @Symbol(SymbolConstants.CONTEXT_PATH) + private String contextPath; + + @Override + public void writeReport(PrintWriter writer, Throwable exception) + { + writeReport(writer, analyzer.analyze(exception)); + } + + private PropertyWriter newPropertyWriter(final PrintWriter writer, Iterable<String> names) + { + final int maxPropertyNameLength = F.flow(names).map(STRING_TO_LENGTH).reduce(MAX, 0); + + final String propertyNameFormat = " %" + maxPropertyNameLength + "s: %s%n"; + + return new PropertyWriter() + { + @SuppressWarnings("rawtypes") + @Override + public void write(String name, Object value) + { + if (value.getClass().isArray()) + { + write(name, toList(value)); + return; + } + + if (value instanceof Iterable) + { + boolean first = true; + Iterable iterable = (Iterable) value; + Iterator i = iterable.iterator(); + while (i.hasNext()) + { + if (first) + { + writer.printf(propertyNameFormat, name, i.next()); + first = false; + } else + { + for (int j = 0; j < maxPropertyNameLength + 4; j++) + writer.write(' '); + + writer.println(i.next()); + } + } + return; + } + + writer.printf(propertyNameFormat, name, value); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private List toList(Object array) + { + int count = Array.getLength(array); + List result = new ArrayList(count); + for (int i = 0; i < count; i++) + { + result.add(Array.get(array, i)); + } + return result; + } + }; + } + + @Override + public void writeReport(final PrintWriter writer, ExceptionAnalysis analysis) + { + writer.printf("EXCEPTION STACK:%n%n"); + + // Figure out what all the property names are so that we can set the width of the column that lists + // property names. + Flow<String> propertyNames = F.flow(analysis.getExceptionInfos()) + .mapcat(EXCEPTION_INFO_TO_PROPERTY_NAMES).append("Exception", "Message"); + + PropertyWriter pw = newPropertyWriter(writer, propertyNames); + + boolean first = true; + + for (ExceptionInfo info : analysis.getExceptionInfos()) + { + if (first) + { + writer.println(); + first = false; + } + + pw.write("Exception", info.getClassName()); + pw.write("Message", info.getMessage()); + + for (String name : info.getPropertyNames()) + { + pw.write(name, info.getProperty(name)); + } + if (!info.getStackTrace().isEmpty()) + { + writer.printf("%n Stack trace:%n%n"); + for (StackTraceElement e : info.getStackTrace()) + { + writer.printf(" - %s%n", e.toString()); + } + } + writer.println(); + } + + Request request = requestGlobals.getRequest(); + + if (request != null) + { + // New PropertyWriter based on the lengths of parameter names and header names, and a sample of + // the literal keys. + + pw = newPropertyWriter(writer, + F.flow(request.getParameterNames()) + .concat(request.getHeaderNames()) + .append("serverName", "removeHost")); + + writer.printf("REQUEST:%n%nBasic Information:%n%n"); + + List<String> flags = CollectionFactory.newList(); + if (request.isXHR()) + { + flags.add("XHR"); + } + if (request.isRequestedSessionIdValid()) + { + flags.add("requestedSessionIdValid"); + } + if (request.isSecure()) + { + flags.add("secure"); + } + pw.write("contextPath", contextPath); + + if (!flags.isEmpty()) + { + pw.write("flags", InternalUtils.joinSorted(flags)); + } + pw.write("method", request.getMethod()); + pw.write("path", request.getPath()); + pw.write("locale", request.getLocale()); + pw.write("serverName", request.getServerName()); + pw.write("remoteHost", request.getRemoteHost()); + + writer.printf("%nHeaders:%n%n"); + + for (String name : request.getHeaderNames()) + { + pw.write(name, request.getHeader(name)); + } + if (!request.getParameterNames().isEmpty()) + { + writer.print("%nParameters:%n"); + for (String name : request.getParameterNames()) + { + // TODO: Support multi-value parameters + pw.write(name, request.getParameters(name)); + } + } + + Session session = request.getSession(false); + + if (session != null) + { + pw = newPropertyWriter(writer, session.getAttributeNames()); + + writer.printf("%nSESSION:%n%n"); + + for (String name : session.getAttributeNames()) + { + pw.write(name, session.getAttribute(name)); + } + } + } + + writer.printf("%nSYSTEM INFORMATION:"); + + Runtime runtime = Runtime.getRuntime(); + + writer.printf("%n%nMemory:%n %,15d bytes free%n %,15d bytes total%n %,15d bytes max%n", + runtime.freeMemory(), + runtime.totalMemory(), + runtime.maxMemory()); + + Thread[] threads = TapestryInternalUtils.getAllThreads(); + + int maxThreadNameLength = 0; + + for (Thread t : threads) + { + maxThreadNameLength = Math.max(maxThreadNameLength, t.getName().length()); + } + + String format = "%n%s %" + maxThreadNameLength + "s %s"; + + writer.printf("%n%,d Threads:", threads.length); + + for (Thread t : threads) + { + writer.printf(format, + Thread.currentThread() == t ? "*" : " ", + t.getName(), + t.getState().name()); + + if (t.isDaemon()) + { + writer.write(", daemon"); + } + + if (!t.isAlive()) + { + writer.write(", NOT alive"); + } + + if (t.isInterrupted()) + { + writer.write(", interrupted"); + } + + if (t.getPriority() != Thread.NORM_PRIORITY) + { + writer.printf(", priority %d", t.getPriority()); + } + } + + // Finish the final line. + writer.println(); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ede56bc6/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java index 77a7161..af276d0 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java @@ -13,74 +13,40 @@ package org.apache.tapestry5.internal.services.exceptions; import org.apache.tapestry5.SymbolConstants; -import org.apache.tapestry5.func.F; -import org.apache.tapestry5.func.Flow; -import org.apache.tapestry5.func.Mapper; -import org.apache.tapestry5.func.Reducer; -import org.apache.tapestry5.internal.TapestryInternalUtils; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Symbol; -import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.internal.util.InternalUtils; -import org.apache.tapestry5.ioc.services.ExceptionAnalysis; -import org.apache.tapestry5.ioc.services.ExceptionAnalyzer; -import org.apache.tapestry5.ioc.services.ExceptionInfo; import org.apache.tapestry5.ioc.util.ExceptionUtils; +import org.apache.tapestry5.services.ExceptionReportWriter; import org.apache.tapestry5.services.ExceptionReporter; -import org.apache.tapestry5.services.Request; -import org.apache.tapestry5.services.RequestGlobals; import org.slf4j.Logger; import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.lang.reflect.Array; -import java.util.*; +import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; @SuppressWarnings("ResultOfMethodCallIgnored") public class ExceptionReporterImpl implements ExceptionReporter { - private static final Reducer<Integer, Integer> MAX = new Reducer<Integer, Integer>() - { - @Override - public Integer reduce(Integer accumulator, Integer element) - { - return Math.max(accumulator, element); - } - }; - - private static final Mapper<String, Integer> STRING_TO_LENGTH = new Mapper<String, Integer>() - { - @Override - public Integer map(String element) - { - return element.length(); - } - }; @Inject @Symbol(SymbolConstants.EXCEPTION_REPORTS_DIR) private File logDir; @Inject - @Symbol(SymbolConstants.CONTEXT_PATH) - private String contextPath; - - @Inject @Symbol(SymbolConstants.RESTRICTIVE_ENVIRONMENT) private boolean restrictive; - @Inject - private ExceptionAnalyzer analyzer; - private final AtomicInteger uid = new AtomicInteger(); @Inject private Logger logger; + @Inject - private RequestGlobals requestGlobals; + private ExceptionReportWriter exceptionReportWriter; @Override public void reportException(Throwable exception) @@ -134,221 +100,18 @@ public class ExceptionReporterImpl implements ExceptionReporter private void writeExceptionToFile(Throwable exception, File log) throws IOException { log.createNewFile(); - ExceptionAnalysis analysis = analyzer.analyze(exception); + PrintWriter writer = null; + try { writer = new PrintWriter(log); - writeException(writer, analysis); + exceptionReportWriter.writeReport(writer, exception); } finally { InternalUtils.close(writer); } } - interface PropertyWriter - { - void write(String name, Object value); - } - - private final static Mapper<ExceptionInfo, Flow<String>> EXCEPTION_INFO_TO_PROPERTY_NAMES = - new Mapper<ExceptionInfo, Flow<String>>() - { - @Override - public Flow<String> map(ExceptionInfo element) - { - return F.flow(element.getPropertyNames()); - } - }; - - private void writeException(final PrintWriter writer, ExceptionAnalysis analysis) - { - final Formatter f = new Formatter(writer); - writer.print("EXCEPTION STACK:\n\n"); - Request request = requestGlobals.getRequest(); - - // Figure out what all the property names are so that we can set the width of the column that lists - // property names. - Flow<String> propertyNames = F.flow(analysis.getExceptionInfos()) - .mapcat(EXCEPTION_INFO_TO_PROPERTY_NAMES).append("Exception type", "Message"); - - if (request != null) - { - propertyNames = propertyNames.concat(request.getParameterNames()).concat(request.getHeaderNames()); - } - - final int maxPropertyNameLength = propertyNames.map(STRING_TO_LENGTH).reduce(MAX, 0); - - final String propertyNameFormat = " %" + maxPropertyNameLength + "s: %s\n"; - - PropertyWriter pw = new PropertyWriter() - { - @SuppressWarnings("rawtypes") - @Override - public void write(String name, Object value) - { - if (value.getClass().isArray()) - { - write(name, toList(value)); - return; - } - - if (value instanceof Iterable) - { - boolean first = true; - Iterable iterable = (Iterable) value; - Iterator i = iterable.iterator(); - while (i.hasNext()) - { - if (first) - { - f.format(propertyNameFormat, name, i.next()); - first = false; - } else - { - for (int j = 0; j < maxPropertyNameLength + 4; j++) - writer.write(' '); - - writer.println(i.next()); - } - } - return; - } - - // TODO: Handling of arrays & collections - f.format(propertyNameFormat, name, value); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private List toList(Object array) - { - int count = Array.getLength(array); - List result = new ArrayList(count); - for (int i = 0; i < count; i++) - { - result.add(Array.get(array, i)); - } - return result; - } - }; - - boolean first = true; - - for (ExceptionInfo info : analysis.getExceptionInfos()) - { - if (first) - { - writer.println(); - first = false; - } - pw.write("Exception type", info.getClassName()); - pw.write("Message", info.getMessage()); - for (String name : info.getPropertyNames()) - { - pw.write(name, info.getProperty(name)); - } - if (!info.getStackTrace().isEmpty()) - { - writer.write("\n Stack trace:\n"); - for (StackTraceElement e : info.getStackTrace()) - { - f.format(" - %s\n", e.toString()); - } - } - writer.println(); - } - - if (request != null) - { - writer.print("REQUEST:\n\nBasic Information:\n"); - List<String> flags = CollectionFactory.newList(); - if (request.isXHR()) - { - flags.add("XHR"); - } - if (request.isRequestedSessionIdValid()) - { - flags.add("requestedSessionIdValid"); - } - if (request.isSecure()) - { - flags.add("secure"); - } - pw.write("contextPath", contextPath); - if (!flags.isEmpty()) - { - pw.write("flags", InternalUtils.joinSorted(flags)); - } - pw.write("method", request.getMethod()); - pw.write("path", request.getPath()); - pw.write("locale", request.getLocale()); - pw.write("serverName", request.getServerName()); - pw.write("remoteHost", request.getRemoteHost()); - writer.print("\nHeaders:\n"); - for (String name : request.getHeaderNames()) - { - pw.write(name, request.getHeader(name)); - } - if (!request.getParameterNames().isEmpty()) - { - writer.print("\nParameters:\n"); - for (String name : request.getParameterNames()) - { - // TODO: Support multi-value parameters - pw.write(name, request.getParameters(name)); - } - } - // TODO: Session if it exists - } - - writer.print("\nSYSTEM INFORMATION:"); - - Runtime runtime = Runtime.getRuntime(); - - f.format("\n\nMemory:\n %,15d bytes free\n %,15d bytes total\n %,15d bytes max\n", - runtime.freeMemory(), - runtime.totalMemory(), - runtime.maxMemory()); - - Thread[] threads = TapestryInternalUtils.getAllThreads(); - - int maxThreadNameLength = 0; - - for (Thread t : threads) - { - maxThreadNameLength = Math.max(maxThreadNameLength, t.getName().length()); - } - - String format = "\n%s %" + maxThreadNameLength + "s %s"; - - f.format("\n%,d Threads:", threads.length); - - for (Thread t : threads) - { - f.format(format, - Thread.currentThread() == t ? "*" : " ", - t.getName(), - t.getState().name()); - if (t.isDaemon()) - { - writer.write(", daemon"); - } - if (!t.isAlive()) - { - writer.write(", NOT alive"); - } - if (t.isInterrupted()) - { - writer.write(", interrupted"); - } - if (t.getPriority() != Thread.NORM_PRIORITY) - { - f.format(", priority %d", t.getPriority()); - } - } - writer.println(); - - f.close(); - } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ede56bc6/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java index d215095..119ca58 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java @@ -38,6 +38,7 @@ import org.apache.tapestry5.internal.services.*; import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateFilter; import org.apache.tapestry5.internal.services.ajax.AjaxResponseRendererImpl; import org.apache.tapestry5.internal.services.ajax.MultiZoneUpdateEventResultProcessor; +import org.apache.tapestry5.internal.services.exceptions.ExceptionReportWriterImpl; import org.apache.tapestry5.internal.services.exceptions.ExceptionReporterImpl; import org.apache.tapestry5.internal.services.linktransform.LinkTransformerImpl; import org.apache.tapestry5.internal.services.linktransform.LinkTransformerInterceptor; @@ -377,6 +378,7 @@ public final class TapestryModule binder.bind(DateUtilities.class, DateUtilitiesImpl.class); binder.bind(PartialTemplateRenderer.class, PartialTemplateRendererImpl.class); binder.bind(ExceptionReporter.class, ExceptionReporterImpl.class); + binder.bind(ExceptionReportWriter.class, ExceptionReportWriterImpl.class); binder.bind(ComponentOverride.class, ComponentOverrideImpl.class).eagerLoad(); binder.bind(Html5Support.class, Html5SupportImpl.class); } @@ -2721,7 +2723,7 @@ public final class TapestryModule configuration.addInstance("Maven", MavenComponentLibraryInfoSource.class); configuration.add("TapestryCore", new TapestryCoreComponentLibraryInfoSource()); } - + private static final class TapestryCoreComponentLibraryInfoSource implements ComponentLibraryInfoSource { @@ -2731,9 +2733,9 @@ public final class TapestryModule ComponentLibraryInfo info = null; if (libraryMapping.libraryName.equals("core")) { - + info = new ComponentLibraryInfo(); - + // the information above will probably not change in the future, or change very // infrequently, so I see no problem in hardwiring them here. info.setArtifactId("tapestry-core"); @@ -2747,18 +2749,17 @@ public final class TapestryModule info.setIssueTrackerUrl("https://issues.apache.org/jira/browse/TAP5"); info.setHomepageUrl("http://tapestry.apache.org"); info.setLibraryMapping(libraryMapping); - + final InputStream inputStream = TapestryModule.class.getResourceAsStream( "/META-INF/gradle/org.apache.tapestry/tapestry-core/project.properties"); - + if (inputStream != null) { Properties properties = new Properties(); try { properties.load(inputStream); - } - catch (IOException e) + } catch (IOException e) { throw new RuntimeException(e); } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ede56bc6/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReportWriter.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReportWriter.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReportWriter.java new file mode 100644 index 0000000..1f834b0 --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReportWriter.java @@ -0,0 +1,38 @@ +// Licensed 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.tapestry5.services; + +import org.apache.tapestry5.ioc.services.ExceptionAnalysis; + +import java.io.PrintWriter; + +/** + * Used by the default {@link org.apache.tapestry5.services.ExceptionReporter} implementation to convert an exception into + * a stream of text that can be stored to a file. Other applications include sending the text via e-mail or other messaging + * service. + * + * @since 5.4 + */ +public interface ExceptionReportWriter +{ + /** + * Analyzes the exception (using the {@link org.apache.tapestry5.ioc.services.ExceptionAnalyzer} service) + * and then writes the result to the writer. + */ + void writeReport(PrintWriter writer, Throwable exception); + + /** + * Writes the analyzed exception to the writer. + */ + void writeReport(PrintWriter writer, ExceptionAnalysis exception); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ede56bc6/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReporter.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReporter.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReporter.java index 582f849..070aa84 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReporter.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ExceptionReporter.java @@ -25,13 +25,15 @@ package org.apache.tapestry5.services; * * @see org.apache.tapestry5.SymbolConstants#EXCEPTION_REPORTS_DIR * @see org.apache.tapestry5.services.RequestExceptionHandler + * @see org.apache.tapestry5.services.ExceptionReportWriter */ public interface ExceptionReporter { /** * Used to communicate to the page what exception is to be reported. * - * @param exception runtime exception thrown during processing of the request + * @param exception + * runtime exception thrown during processing of the request */ void reportException(Throwable exception); } \ No newline at end of file