    001    // Copyright 2008, 2009, 2010, 2011 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.ioc.internal;
    016
    017    import org.apache.tapestry5.ioc.Invokable;
    018    import org.apache.tapestry5.ioc.OperationTracker;
    019    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    020    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    021    import org.apache.tapestry5.ioc.util.Stack;
    022    import org.slf4j.Logger;
    023
    024    /**
    025     * Core implementation that manages a logger and catches and reports exception.
    026     *
    027     * @see org.apache.tapestry5.ioc.internal.PerThreadOperationTracker
    028     */
    029    public class OperationTrackerImpl implements OperationTracker
    030    {
    031        private final Logger logger;
    032
    033        private final Stack<String> operations = CollectionFactory.newStack();
    034
    035        private boolean logged;
    036
    037        public OperationTrackerImpl(Logger logger)
    038        {
    039            this.logger = logger;
    040        }
    041
    042        public void run(String description, final Runnable operation)
    043        {
    044            assert InternalUtils.isNonBlank(description);
    045            assert operation != null;
    046
    047            invoke(description, new Invokable<Void>()
    048            {
    049                public Void invoke()
    050                {
    051                    operation.run();
    052
    053                    return null;
    054                }
    055            });
    056        }
    057
    058        public <T> T invoke(String description, Invokable<T> operation)
    059        {
    060            assert InternalUtils.isNonBlank(description);
    061            assert operation != null;
    062
    063            long startNanos = System.nanoTime();
    064
    065            if (logger.isDebugEnabled())
    066            {
    067                logger.debug(String.format("[%3d] --> %s", operations.getDepth() + 1, description));
    068            }
    069
    070            operations.push(description);
    071
    072            try
    073            {
    074                T result = operation.invoke();
    075
    076                if (logger.isDebugEnabled())
    077                {
    078                    long elapsedNanos = System.nanoTime() - startNanos;
    079                    double elapsedMillis = ((double) elapsedNanos) / 1000000.d;
    080
    081                    logger.debug(String.format("[%3d] <-- %s [%,.2f ms]", operations.getDepth(), description, elapsedMillis));
    082                }
    083
    084                return result;
    085
    086            } catch (RuntimeException ex)
    087            {
    088                logAndRethrow(ex);
    089
    090                throw ex;
    091            } catch (Error ex)
    092            {
    093                if (!logged)
    094                {
    095                    log(ex);
    096                    logged = true;
    097                }
    098
    099                throw ex;
    100            } finally
    101            {
    102                operations.pop();
    103
    104                // We've finally backed out of the operation stack ... but there may be more to come!
    105
    106                if (operations.isEmpty())
    107                    logged = false;
    108            }
    109
    110
    111        }
    112
    113        private void logAndRethrow(Throwable ex)
    114        {
    115            if (!logged)
    116            {
    117                String[] trace = log(ex);
    118
    119                logged = true;
    120
    121                throw new OperationException(ex, trace);
    122            }
    123        }
    124
    125        private String[] log(Throwable ex)
    126        {
    127            logger.error(InternalUtils.toMessage(ex));
    128            logger.error("Operations trace:");
    129
    130            Object[] snapshot = operations.getSnapshot();
    131            String[] trace = new String[snapshot.length];
    132
    133            for (int i = 0; i < snapshot.length; i++)
    134            {
    135                trace[i] = snapshot[i].toString();
    136
    137                logger.error(String.format("[%2d] %s", i + 1, trace[i]));
    138            }
    139            return trace;
    140        }
    141
    142        boolean isEmpty()
    143        {
    144            return operations.isEmpty();
    145        }
    146    }