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
015package org.apache.tapestry5.ioc.internal;
016
017import org.apache.tapestry5.ioc.Invokable;
018import org.apache.tapestry5.ioc.OperationTracker;
019import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021import org.apache.tapestry5.ioc.util.Stack;
022import 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 */
029public 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}