    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.SymbolConstants;
    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.internal.TapestryInternalUtils;
    021import org.apache.tapestry5.ioc.annotations.Inject;
    022import org.apache.tapestry5.ioc.annotations.Symbol;
    023import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    024import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    025import org.apache.tapestry5.ioc.services.ExceptionAnalysis;
    026import org.apache.tapestry5.ioc.services.ExceptionAnalyzer;
    027import org.apache.tapestry5.ioc.services.ExceptionInfo;
    028import org.apache.tapestry5.services.ExceptionReportWriter;
    029import org.apache.tapestry5.services.Request;
    030import org.apache.tapestry5.services.RequestGlobals;
    031import org.apache.tapestry5.services.Session;
    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(SymbolConstants.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}