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}