001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.internal.services.exceptions;
014
015import org.apache.tapestry5.commons.util.CollectionFactory;
016import org.apache.tapestry5.func.F;
017import org.apache.tapestry5.func.Flow;
018import org.apache.tapestry5.func.Mapper;
019import org.apache.tapestry5.func.Reducer;
020import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
021import org.apache.tapestry5.http.services.Request;
022import org.apache.tapestry5.http.services.RequestGlobals;
023import org.apache.tapestry5.http.services.Session;
024import org.apache.tapestry5.internal.TapestryInternalUtils;
025import org.apache.tapestry5.ioc.annotations.Inject;
026import org.apache.tapestry5.ioc.annotations.Symbol;
027import org.apache.tapestry5.ioc.internal.util.InternalUtils;
028import org.apache.tapestry5.ioc.services.ExceptionAnalysis;
029import org.apache.tapestry5.ioc.services.ExceptionAnalyzer;
030import org.apache.tapestry5.ioc.services.ExceptionInfo;
031import org.apache.tapestry5.services.ExceptionReportWriter;
032
033import java.io.PrintWriter;
034import java.lang.reflect.Array;
035import java.util.ArrayList;
036import java.util.Iterator;
037import java.util.List;
038
039public class ExceptionReportWriterImpl implements ExceptionReportWriter
040{
041    private static final Reducer<Integer, Integer> MAX = new Reducer<Integer, Integer>()
042    {
043        @Override
044        public Integer reduce(Integer accumulator, Integer element)
045        {
046            return Math.max(accumulator, element);
047        }
048    };
049
050    private static final Mapper<String, Integer> STRING_TO_LENGTH = new Mapper<String, Integer>()
051    {
052        @Override
053        public Integer map(String element)
054        {
055            return element.length();
056        }
057    };
058
059    private final static Mapper<ExceptionInfo, Flow<String>> EXCEPTION_INFO_TO_PROPERTY_NAMES =
060            new Mapper<ExceptionInfo, Flow<String>>()
061            {
062                @Override
063                public Flow<String> map(ExceptionInfo element)
064                {
065                    return F.flow(element.getPropertyNames());
066                }
067            };
068
069    /**
070     * A little closure that understands how to write a key/value pair representing a property.
071     */
072    interface PropertyWriter
073    {
074        void write(String name, Object value);
075    }
076
077    @Inject
078    private ExceptionAnalyzer analyzer;
079
080    @Inject
081    private RequestGlobals requestGlobals;
082
083    @Inject
084    @Symbol(TapestryHttpSymbolConstants.CONTEXT_PATH)
085    private String contextPath;
086
087    @Override
088    public void writeReport(PrintWriter writer, Throwable exception)
089    {
090        writeReport(writer, analyzer.analyze(exception));
091    }
092
093    private PropertyWriter newPropertyWriter(final PrintWriter writer, Iterable<String> names)
094    {
095        final int maxPropertyNameLength = F.flow(names).map(STRING_TO_LENGTH).reduce(MAX, 0);
096
097        final String propertyNameFormat = "  %" + maxPropertyNameLength + "s: %s%n";
098
099        return new PropertyWriter()
100        {
101            @SuppressWarnings("rawtypes")
102            @Override
103            public void write(String name, Object value)
104            {
105                if (value.getClass().isArray())
106                {
107                    write(name, toList(value));
108                    return;
109                }
110
111                if (value instanceof Iterable)
112                {
113                    boolean first = true;
114                    Iterable iterable = (Iterable) value;
115                    Iterator i = iterable.iterator();
116                    while (i.hasNext())
117                    {
118                        if (first)
119                        {
120                            writer.printf(propertyNameFormat, name, i.next());
121                            first = false;
122                        } else
123                        {
124                            for (int j = 0; j < maxPropertyNameLength + 4; j++)
125                                writer.write(' ');
126
127                            writer.println(i.next());
128                        }
129                    }
130                    return;
131                }
132
133                writer.printf(propertyNameFormat, name, value);
134            }
135
136            @SuppressWarnings({"rawtypes", "unchecked"})
137            private List toList(Object array)
138            {
139                int count = Array.getLength(array);
140                List result = new ArrayList(count);
141                for (int i = 0; i < count; i++)
142                {
143                    result.add(Array.get(array, i));
144                }
145                return result;
146            }
147        };
148    }
149
150    @Override
151    public void writeReport(final PrintWriter writer, ExceptionAnalysis analysis)
152    {
153        writer.printf("EXCEPTION STACK:%n%n");
154
155        // Figure out what all the property names are so that we can set the width of the column that lists
156        // property names.
157        Flow<String> propertyNames = F.flow(analysis.getExceptionInfos())
158                .mapcat(EXCEPTION_INFO_TO_PROPERTY_NAMES).append("Exception", "Message");
159
160        PropertyWriter pw = newPropertyWriter(writer, propertyNames);
161
162        boolean first = true;
163
164        for (ExceptionInfo info : analysis.getExceptionInfos())
165        {
166            if (first)
167            {
168                writer.println();
169                first = false;
170            }
171
172            pw.write("Exception", info.getClassName());
173            pw.write("Message", info.getMessage());
174
175            for (String name : info.getPropertyNames())
176            {
177                pw.write(name, info.getProperty(name));
178            }
179            if (!info.getStackTrace().isEmpty())
180            {
181                writer.printf("%n  Stack trace:%n%n");
182                for (StackTraceElement e : info.getStackTrace())
183                {
184                    writer.printf("  - %s%n", e.toString());
185                }
186            }
187            writer.println();
188        }
189
190        Request request = requestGlobals.getRequest();
191
192        if (request != null)
193        {
194            // New PropertyWriter based on the lengths of parameter names and header names, and a sample of
195            // the literal keys.
196
197            pw = newPropertyWriter(writer,
198                    F.flow(request.getParameterNames())
199                            .concat(request.getHeaderNames())
200                            .append("serverName", "removeHost"));
201
202            writer.printf("REQUEST:%n%nBasic Information:%n%n");
203
204            List<String> flags = CollectionFactory.newList();
205            if (request.isXHR())
206            {
207                flags.add("XHR");
208            }
209            if (request.isRequestedSessionIdValid())
210            {
211                flags.add("requestedSessionIdValid");
212            }
213            if (request.isSecure())
214            {
215                flags.add("secure");
216            }
217            pw.write("contextPath", contextPath);
218
219            if (!flags.isEmpty())
220            {
221                pw.write("flags", InternalUtils.joinSorted(flags));
222            }
223            pw.write("method", request.getMethod());
224            pw.write("path", request.getPath());
225            pw.write("locale", request.getLocale());
226            pw.write("serverName", request.getServerName());
227            pw.write("remoteHost", request.getRemoteHost());
228
229            writer.printf("%nHeaders:%n%n");
230
231            for (String name : request.getHeaderNames())
232            {
233                pw.write(name, request.getHeader(name));
234            }
235            if (!request.getParameterNames().isEmpty())
236            {
237                writer.printf("%nParameters:%n");
238                for (String name : request.getParameterNames())
239                {
240                    // TODO: Support multi-value parameters
241                    pw.write(name, request.getParameters(name));
242                }
243            }
244
245            Session session = request.getSession(false);
246
247            if (session != null)
248            {
249                pw = newPropertyWriter(writer, session.getAttributeNames());
250
251                writer.printf("%nSESSION:%n%n");
252
253                for (String name : session.getAttributeNames())
254                {
255                    pw.write(name, session.getAttribute(name));
256                }
257            }
258        }
259
260        writer.printf("%nSYSTEM INFORMATION:");
261
262        Runtime runtime = Runtime.getRuntime();
263
264        writer.printf("%n%nMemory:%n  %,15d bytes free%n  %,15d bytes total%n  %,15d bytes max%n",
265                runtime.freeMemory(),
266                runtime.totalMemory(),
267                runtime.maxMemory());
268
269        Thread[] threads = TapestryInternalUtils.getAllThreads();
270
271        int maxThreadNameLength = 0;
272
273        for (Thread t : threads)
274        {
275            maxThreadNameLength = Math.max(maxThreadNameLength, t.getName().length());
276        }
277
278        String format = "%n%s %" + maxThreadNameLength + "s %s";
279
280        writer.printf("%n%,d Threads:", threads.length);
281
282        for (Thread t : threads)
283        {
284            writer.printf(format,
285                    Thread.currentThread() == t ? "*" : " ",
286                    t.getName(),
287                    t.getState().name());
288
289            if (t.isDaemon())
290            {
291                writer.write(", daemon");
292            }
293
294            if (!t.isAlive())
295            {
296                writer.write(", NOT alive");
297            }
298
299            if (t.isInterrupted())
300            {
301                writer.write(", interrupted");
302            }
303
304            if (t.getPriority() != Thread.NORM_PRIORITY)
305            {
306                writer.printf(", priority %d", t.getPriority());
307            }
308        }
309
310        // Finish the final line.
311        writer.println();
312    }
313}