/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.portlet.rendering.worker; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.portal.portlet.om.IPortletWindowId; import org.apereo.portal.portlet.rendering.IPortletRenderer; import org.apereo.portal.portlet.rendering.PortletRenderResult; import org.apereo.portal.portlet.rendering.RenderPortletOutputHandler; import org.apereo.portal.portlets.error.PortletErrorController; import org.apereo.portal.utils.web.PortletHttpServletRequestWrapper; /** * Worker used to execute render requests on the error portlet. Does not use any thread-pool code to * make sure the error portlet still renders in the event of the thread pool being broken. * */ final class PortletFailureExecutionWorker implements IPortletFailureExecutionWorker { private static final int SIGNAL_THE_OPERATION_DOES_NOT_TIMEOUT = -1; protected final Log logger = LogFactory.getLog(this.getClass()); private final Map<String, Object> executionAttributes = new ConcurrentHashMap<String, Object>(); private final IPortletRenderer portletRenderer; private final List<IPortletExecutionInterceptor> interceptors; private final HttpServletRequest request; private final HttpServletResponse response; private final IPortletWindowId errorPortletWindowId; private final IPortletWindowId failedPortletWindowId; private final String failedPortletFname; private final Exception cause; private PortletRenderResult portletRenderResult; private String output; private boolean retrieved = false; private long submitted = 0; private long completed = 0; public PortletFailureExecutionWorker( IPortletRenderer portletRenderer, List<IPortletExecutionInterceptor> interceptors, HttpServletRequest request, HttpServletResponse response, IPortletWindowId errorPortletWindowId, IPortletWindowId failedPortletWindowId, String failedPortletFname, Exception cause) { this.portletRenderer = portletRenderer; this.interceptors = interceptors; this.request = request; this.response = response; this.errorPortletWindowId = errorPortletWindowId; this.failedPortletWindowId = failedPortletWindowId; this.failedPortletFname = failedPortletFname; this.cause = cause; } @Override public ExecutionType getExecutionType() { return ExecutionType.FAILURE; } @Override public void submit() { if (this.submitted > 0) { throw new IllegalStateException( this.getClass().getSimpleName() + " for " + this.getPortletWindowId() + " has already been submitted."); } this.submitted = System.currentTimeMillis(); //Run pre-submit interceptors for (final IPortletExecutionInterceptor interceptor : this.interceptors) { interceptor.preSubmit(request, response, this); } } private void doPostExecution(Exception e) { //Iterate over handlers in reverse for post execution final ListIterator<IPortletExecutionInterceptor> listIterator = this.interceptors.listIterator(this.interceptors.size()); while (listIterator.hasPrevious()) { final IPortletExecutionInterceptor interceptor = listIterator.previous(); try { interceptor.postExecution(request, response, this, e); } catch (Throwable ex2) { logger.error("HandlerInterceptor.postExecution threw exception", ex2); } } } @Override public String getOutput(long timeout) throws Exception { this.get(timeout); return this.output; } @Override public long waitForStart(long timeout) throws InterruptedException { this.renderError(timeout); return this.submitted; } @Override public synchronized PortletRenderResult get(long timeout) throws Exception { this.retrieved = true; this.renderError(timeout); return this.portletRenderResult; } /* (non-Javadoc) * @see org.apereo.portal.portlet.rendering.worker.IPortletExecutionWorker#cancel() */ @Override public void cancel() { //NOOP } /* (non-Javadoc) * @see org.apereo.portal.portlet.rendering.worker.IPortletExecutionWorker#getCancelCount() */ @Override public int getCancelCount() { return 0; } protected synchronized void renderError(long timeout) { //Make sure the error rendering only happens once if (this.completed > 0) { return; } //Wrap the request to scope the attributes to just this execution final PortletHttpServletRequestWrapper wrappedRequest = new PortletHttpServletRequestWrapper(request); wrappedRequest.setAttribute( PortletErrorController.REQUEST_ATTRIBUTE__CURRENT_FAILED_PORTLET_WINDOW_ID, failedPortletWindowId); wrappedRequest.setAttribute( PortletErrorController.REQUEST_ATTRIBUTE__CURRENT_EXCEPTION_CAUSE, cause); //Run pre-execution interceptors for (final IPortletExecutionInterceptor interceptor : this.interceptors) { interceptor.preExecution(request, response, this); } //Aggressive exception handling to make sure at least something is written out when an error happens. try { final String characterEncoding = response.getCharacterEncoding(); final RenderPortletOutputHandler renderPortletOutputHandler = new RenderPortletOutputHandler(characterEncoding); this.portletRenderResult = this.portletRenderer.doRenderMarkup( errorPortletWindowId, wrappedRequest, response, renderPortletOutputHandler); doPostExecution(null); this.output = renderPortletOutputHandler.getOutput(); } catch (Exception e) { doPostExecution(e); this.logger.error("Exception while dispatching to error handling portlet", e); this.output = "Error Portlet Unavailable. Please contact your portal adminstrators."; } this.completed = System.currentTimeMillis(); } @Override public Object setExecutionAttribute(String name, Object value) { if (value == null) { return executionAttributes.remove(name); } return this.executionAttributes.put(name, value); } @Override public Object getExecutionAttribute(String name) { return this.executionAttributes.get(name); } @Override public IPortletWindowId getPortletWindowId() { return this.failedPortletWindowId; } /* (non-Javadoc) * @see org.apereo.portal.portlet.rendering.worker.IPortletExecutionContext#getPortletFname() */ @Override public String getPortletFname() { return this.failedPortletFname; } @Override public boolean isSubmitted() { return this.submitted > 0; } @Override public boolean isStarted() { return this.submitted > 0; } @Override public boolean isComplete() { return this.completed > 0; } @Override public boolean isRetrieved() { return this.retrieved; } @Override public long getSubmittedTime() { return this.submitted; } @Override public long getStartedTime() { return this.submitted; } @Override public long getCompleteTime() { return this.completed; } @Override public long getWait() { return 0; } @Override public long getDuration() { return this.completed - this.submitted; } @Override public long getApplicableTimeout() { return SIGNAL_THE_OPERATION_DOES_NOT_TIMEOUT; } }