001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.lifecycle;
017
018import java.util.Deque;
019import java.util.HashMap;
020import java.util.LinkedHashSet;
021import java.util.LinkedList;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.log4j.Logger;
026import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
027import org.kuali.rice.krad.uif.component.Component;
028import org.kuali.rice.krad.uif.freemarker.LifecycleRenderingContext;
029import org.kuali.rice.krad.uif.util.LifecycleElement;
030import org.kuali.rice.krad.uif.util.ProcessLogger;
031import org.kuali.rice.krad.uif.view.DefaultExpressionEvaluator;
032import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
033import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
034import org.springframework.util.StringUtils;
035
036/**
037 * Single-threaded view lifecycle processor implementation.
038 * 
039 * @author Kuali Rice Team (rice.collab@kuali.org)
040 */
041public class SynchronousViewLifecycleProcessor extends ViewLifecycleProcessorBase {
042    private static final Logger LOG = Logger.getLogger(SynchronousViewLifecycleProcessor.class);
043
044    // pending lifecycle phases.
045    private final Deque<ViewLifecyclePhase> pendingPhases = new LinkedList<ViewLifecyclePhase>();
046
047    // the phase currently active on this lifecycle.
048    private ViewLifecyclePhase activePhase;
049
050    // the rendering context.
051    private LifecycleRenderingContext renderingContext;
052
053    // the expression evaluator to use with this lifecycle.
054    private final ExpressionEvaluator expressionEvaluator;
055
056    private static String getTracePath(ViewLifecyclePhase phase) {
057        Component parent = phase.getParent();
058        if (parent == null) {
059            return "";
060        } else {
061            return phase.getParent().getViewPath();
062        }
063    }
064    
065    private static final class TraceNode {
066        private final String path;
067        private StringBuilder buffer = new StringBuilder();
068        private Set<String> childPaths = new LinkedHashSet<String>();
069
070        private TraceNode(ViewLifecyclePhase phase) {
071            path = getTracePath(phase);
072        }
073        
074        private void startTrace(ViewLifecyclePhase phase) {
075            try {
076                LifecycleElement element = phase.getElement();
077                
078                String parentPath = phase.getParentPath();
079                if (StringUtils.hasLength(parentPath)) {
080                    childPaths.add(phase.getParentPath());
081                }
082                
083                buffer.append('\n');
084                for (int i = 0; i < phase.getDepth(); i++) {
085                    buffer.append("  ");
086                }
087                buffer.append(phase.getViewPath());
088                buffer.append(' ');
089                buffer.append(phase.getEndViewStatus());
090                buffer.append(' ');
091                buffer.append(element.getViewStatus());
092                buffer.append(' ');
093                buffer.append(element.isRender());
094                buffer.append(' ');
095                buffer.append(element.getClass().getSimpleName());
096                buffer.append(' ');
097                buffer.append(element.getId());
098                buffer.append(' ');
099            } catch (Throwable e) {
100                LOG.warn("Error tracing lifecycle", e);
101            }
102        }
103        
104        private void finishTrace(long phaseStartTime, Throwable error) {
105            if (error == null) {
106                buffer.append(" done ");
107            } else {
108                buffer.append(" ERROR ");
109            }
110            buffer.append(ProcessLogger.intervalToString(System.currentTimeMillis() - phaseStartTime));
111        }
112    }
113
114    private final Map<String, TraceNode> trace = ViewLifecycle.isTrace() ? new HashMap<String, TraceNode>() : null;
115    
116    private TraceNode getTraceNode(ViewLifecyclePhase phase) {
117        if (trace == null) {
118            return null;
119        }
120        
121        String tracePath = getTracePath(phase);
122        TraceNode traceNode = trace.get(tracePath);
123        
124        if (traceNode == null) {
125            traceNode = new TraceNode(phase);
126            trace.put(tracePath, traceNode);
127        }
128        
129        return traceNode;
130    }
131
132    /**
133     * Creates a new synchronous processor for a lifecycle.
134     * 
135     * @param lifecycle The lifecycle to process.
136     */
137    public SynchronousViewLifecycleProcessor(ViewLifecycle lifecycle) {
138        super(lifecycle);
139
140        // The null conditions noted here should not happen in full configured environments
141        // Conditional fallback support is in place primary for unit testing.
142        ExpressionEvaluatorFactory expressionEvaluatorFactory;
143        if (lifecycle.helper == null) {
144            LOG.warn("No helper is defined for the view lifecycle, using global expression evaluation factory");
145            expressionEvaluatorFactory = KRADServiceLocatorWeb.getExpressionEvaluatorFactory();
146        } else {
147            expressionEvaluatorFactory = lifecycle.helper.getExpressionEvaluatorFactory();
148        }
149
150        if (expressionEvaluatorFactory == null) {
151            LOG.warn("No global expression evaluation factory is defined, using DefaultExpressionEvaluator");
152            expressionEvaluator = new DefaultExpressionEvaluator();
153        } else {
154            expressionEvaluator = expressionEvaluatorFactory.createExpressionEvaluator();
155        }
156    }
157
158    /**
159     * {@inheritDoc}
160     */
161    @Override
162    public void offerPendingPhase(ViewLifecyclePhase pendingPhase) {
163        pendingPhases.offer(pendingPhase);
164    }
165
166    /**
167     * {@inheritDoc}
168     */
169    @Override
170    public void pushPendingPhase(ViewLifecyclePhase phase) {
171        pendingPhases.push(phase);
172    }
173
174    /**
175     * {@inheritDoc}
176     */
177    @Override
178    public void performPhase(ViewLifecyclePhase initialPhase) {
179        long startTime = System.currentTimeMillis();
180        TraceNode initialNode = getTraceNode(initialPhase);
181
182        offerPendingPhase(initialPhase);
183        while (!pendingPhases.isEmpty()) {
184            ViewLifecyclePhase pendingPhase = pendingPhases.poll();
185            long phaseStartTime = System.currentTimeMillis();
186
187            try {
188                if (trace != null) {
189                    getTraceNode(pendingPhase).startTrace(pendingPhase);
190                }
191
192                pendingPhase.run();
193
194                if (trace != null) {
195                    getTraceNode(pendingPhase).finishTrace(phaseStartTime, null);
196                }
197
198            } catch (Throwable e) {
199                if (trace != null) {
200                    getTraceNode(pendingPhase).finishTrace(phaseStartTime, e);
201                }
202
203                if (e instanceof RuntimeException) {
204                    throw (RuntimeException) e;
205                } else if (e instanceof Error) {
206                    throw (Error) e;
207                } else {
208                    throw new IllegalStateException(e);
209                }
210            }
211        }
212
213        if (trace != null) {
214            assert initialNode != null : initialPhase;
215            Deque<TraceNode> msgQueue = new LinkedList<TraceNode>();
216            StringBuilder msg = new StringBuilder();
217
218            msgQueue.push(initialNode);
219            while (!msgQueue.isEmpty()) {
220                TraceNode traceNode = msgQueue.pop();
221                assert traceNode != null : msg + " " + trace.keySet();
222                assert traceNode.buffer != null : traceNode.path;
223
224                msg.append(traceNode.buffer);
225
226                for (String childPath : traceNode.childPaths) {
227                    TraceNode child = trace.get(traceNode.path + (traceNode.path.equals("") ? "" : ".") + childPath);
228                    if (child != null) {
229                        msgQueue.push(child);
230                    }
231                }
232            }
233
234            LOG.info("Lifecycle phase processing completed in "
235                    + ProcessLogger.intervalToString(System.currentTimeMillis() - startTime) + msg);
236            trace.clear();
237        }
238    }
239
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    public ViewLifecyclePhase getActivePhase() {
245        return activePhase;
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    public LifecycleRenderingContext getRenderingContext() {
252        if (renderingContext == null && ViewLifecycle.isRenderInLifecycle()) {
253            ViewLifecycle lifecycle = getLifecycle();
254            this.renderingContext = new LifecycleRenderingContext(lifecycle.model, lifecycle.request);
255        }
256
257        return this.renderingContext;
258    }
259
260    /**
261     * {@inheritDoc}
262     */
263    @Override
264    public ExpressionEvaluator getExpressionEvaluator() {
265        return this.expressionEvaluator;
266    }
267
268    /**
269     * {@inheritDoc}
270     */
271    @Override
272    void setActivePhase(ViewLifecyclePhase phase) {
273        if (activePhase != null && phase != null) {
274            throw new IllegalStateException("Another phase is already active on this lifecycle thread " + activePhase);
275        }
276
277        activePhase = phase;
278    }
279
280}