/**
* 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;
import java.io.IOException;
import javax.portlet.CacheControl;
import javax.portlet.Event;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.pluto.container.PortletContainer;
import org.apache.pluto.container.PortletContainerException;
import org.apereo.portal.AuthorizationException;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.api.portlet.PortletDelegationLocator;
import org.apereo.portal.events.IPortletExecutionEventFactory;
import org.apereo.portal.portlet.PortletDispatchException;
import org.apereo.portal.portlet.container.cache.CacheControlImpl;
import org.apereo.portal.portlet.container.cache.CacheState;
import org.apereo.portal.portlet.container.cache.CachedPortletData;
import org.apereo.portal.portlet.container.cache.CachedPortletResourceData;
import org.apereo.portal.portlet.container.cache.CachingPortletOutputHandler;
import org.apereo.portal.portlet.container.cache.CachingPortletResourceOutputHandler;
import org.apereo.portal.portlet.container.cache.HeaderSettingCacheControl;
import org.apereo.portal.portlet.container.cache.IPortletCacheControlService;
import org.apereo.portal.portlet.container.cache.PortletCachingHeaderUtils;
import org.apereo.portal.portlet.container.services.AdministrativeRequestListenerController;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.portlet.registry.IPortletWindowRegistry;
import org.apereo.portal.portlet.session.PortletSessionAdministrativeRequestListener;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.IPersonManager;
import org.apereo.portal.services.AuthorizationService;
import org.apereo.portal.url.IPortalRequestInfo;
import org.apereo.portal.url.IUrlSyntaxProvider;
import org.apereo.portal.url.ParameterMap;
import org.apereo.portal.utils.web.PortletHttpServletRequestWrapper;
import org.apereo.portal.utils.web.PortletHttpServletResponseWrapper;
import org.apereo.portal.utils.web.PortletMimeHttpServletResponseWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Executes methods on portlets using Pluto
*
*/
@Service
public class PortletRendererImpl implements IPortletRenderer {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private IPersonManager personManager;
private IPortletWindowRegistry portletWindowRegistry;
private PortletContainer portletContainer;
private PortletDelegationLocator portletDelegationLocator;
private IPortletCacheControlService portletCacheControlService;
private IPortletExecutionEventFactory portalEventFactory;
private IUrlSyntaxProvider urlSyntaxProvider;
@Autowired
public void setUrlSyntaxProvider(IUrlSyntaxProvider urlSyntaxProvider) {
this.urlSyntaxProvider = urlSyntaxProvider;
}
@Autowired
public void setPortalEventFactory(IPortletExecutionEventFactory portalEventFactory) {
this.portalEventFactory = portalEventFactory;
}
@Autowired
public void setPersonManager(IPersonManager personManager) {
this.personManager = personManager;
}
@Autowired
public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
this.portletWindowRegistry = portletWindowRegistry;
}
@Autowired
public void setPortletContainer(PortletContainer portletContainer) {
this.portletContainer = portletContainer;
}
@Autowired
public void setPortletDelegationLocator(PortletDelegationLocator portletDelegationLocator) {
this.portletDelegationLocator = portletDelegationLocator;
}
/** @param portletCacheControlService the portletCacheControlService to set */
@Autowired
public void setPortletCacheControlService(
IPortletCacheControlService portletCacheControlService) {
this.portletCacheControlService = portletCacheControlService;
}
/*
* PLT 22.1 If the content of a portlet is cached and the portlet is target of request
* with an action-type semantic (e.g. an action or event call), the portlet container should discard the cache and
* invoke the corresponding request handling methods of the portlet like processAction,or processEvent.
*/
@Override
public long doAction(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);
final IPortletWindow portletWindow =
this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse =
new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);
//Execute the action,
this.logger.debug("Executing portlet action for window '{}'", portletWindow);
final long start = System.nanoTime();
try {
this.portletContainer.doAction(
portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
} catch (PortletException pe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing action.",
portletWindow,
pe);
} catch (PortletContainerException pce) {
throw new PortletDispatchException(
"The portlet container threw an exception while executing action on portlet window '"
+ portletWindow
+ "'.",
portletWindow,
pce);
} catch (IOException ioe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing action.",
portletWindow,
ioe);
}
final long executionTime = System.nanoTime() - start;
this.portalEventFactory.publishPortletActionExecutionEvent(
httpServletRequest, this, portletWindowId, executionTime);
return executionTime;
}
/*
* PLT 22.1 If the content of a portlet is cached and the portlet is target of request
* with an action-type semantic (e.g. an action or event call), the portlet container should discard the cache and
* invoke the corresponding request handling methods of the portlet like processAction,or processEvent.
*
* (non-Javadoc)
* @see org.apereo.portal.portlet.rendering.IPortletRenderer#doEvent(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.portlet.Event)
*/
@Override
public long doEvent(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Event event) {
this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);
final IPortletWindow portletWindow =
this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse =
new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);
//Execute the action,
this.logger.debug("Executing portlet event for window '{}'", portletWindow);
final long start = System.nanoTime();
try {
this.portletContainer.doEvent(
portletWindow.getPlutoPortletWindow(),
httpServletRequest,
httpServletResponse,
event);
} catch (PortletException pe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing event.",
portletWindow,
pe);
} catch (PortletContainerException pce) {
throw new PortletDispatchException(
"The portlet container threw an exception while executing event on portlet window '"
+ portletWindow
+ "'.",
portletWindow,
pce);
} catch (IOException ioe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing event.",
portletWindow,
ioe);
}
final long executionTime = System.nanoTime() - start;
this.portalEventFactory.publishPortletEventExecutionEvent(
httpServletRequest, this, portletWindowId, executionTime, event.getQName());
return executionTime;
}
@Override
public PortletRenderResult doRenderHeader(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
PortletOutputHandler portletOutputHandler)
throws IOException {
// enforce CONFIG mode restriction through its enforcement in doRender();
return doRender(
portletWindowId,
httpServletRequest,
httpServletResponse,
portletOutputHandler,
RenderPart.HEADERS);
}
/**
* Interacts with the {@link IPortletCacheControlService} to determine if the markup should come
* from cache or not. If cached data doesn't exist or is expired, this delegates to {@link
* #doRender(IPortletWindowId, HttpServletRequest, HttpServletResponse, PortletOutputHandler,
* RenderPart)}.
*
* @throws IOException
*/
@Override
public PortletRenderResult doRenderMarkup(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
PortletOutputHandler portletOutputHandler)
throws IOException {
// enforce CONFIG mode access restriction through its enforcement in the doRender() impl.
return doRender(
portletWindowId,
httpServletRequest,
httpServletResponse,
portletOutputHandler,
RenderPart.MARKUP);
}
/** Describes the part of the render request and defines the part specific behaviors */
protected enum RenderPart {
HEADERS(PortletRequest.RENDER_HEADERS) {
@Override
public CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult>
getCacheState(
IPortletCacheControlService portletCacheControlService,
HttpServletRequest request,
IPortletWindowId portletWindowId) {
return portletCacheControlService.getPortletRenderHeaderState(
request, portletWindowId);
}
@Override
public void cachePortletOutput(
IPortletCacheControlService portletCacheControlService,
IPortletWindowId portletWindowId,
HttpServletRequest request,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult>
cacheState,
CachedPortletData<PortletRenderResult> cachedPortletData) {
portletCacheControlService.cachePortletRenderHeaderOutput(
portletWindowId, request, cacheState, cachedPortletData);
}
@Override
public void publishRenderExecutionEvent(
IPortletExecutionEventFactory portalEventFactory,
PortletRendererImpl source,
HttpServletRequest request,
IPortletWindowId portletWindowId,
long executionTime,
boolean targeted,
boolean cached) {
portalEventFactory.publishPortletRenderHeaderExecutionEvent(
request, source, portletWindowId, executionTime, targeted, cached);
}
},
MARKUP(PortletRequest.RENDER_MARKUP) {
@Override
public CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult>
getCacheState(
IPortletCacheControlService portletCacheControlService,
HttpServletRequest request,
IPortletWindowId portletWindowId) {
return portletCacheControlService.getPortletRenderState(request, portletWindowId);
}
@Override
public void cachePortletOutput(
IPortletCacheControlService portletCacheControlService,
IPortletWindowId portletWindowId,
HttpServletRequest request,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult>
cacheState,
CachedPortletData<PortletRenderResult> cachedPortletData) {
portletCacheControlService.cachePortletRenderOutput(
portletWindowId, request, cacheState, cachedPortletData);
}
@Override
public void publishRenderExecutionEvent(
IPortletExecutionEventFactory portalEventFactory,
PortletRendererImpl source,
HttpServletRequest request,
IPortletWindowId portletWindowId,
long executionTime,
boolean targeted,
boolean cached) {
portalEventFactory.publishPortletRenderExecutionEvent(
request, source, portletWindowId, executionTime, targeted, cached);
}
};
private final String renderPart;
private RenderPart(String renderPart) {
this.renderPart = renderPart;
}
/** Get the cache state for the request */
public abstract CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult>
getCacheState(
IPortletCacheControlService portletCacheControlService,
HttpServletRequest request,
IPortletWindowId portletWindowId);
/** Cache the portlet output */
public abstract void cachePortletOutput(
IPortletCacheControlService portletCacheControlService,
IPortletWindowId portletWindowId,
HttpServletRequest request,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
CachedPortletData<PortletRenderResult> cachedPortletData);
/** Public portlet event */
public abstract void publishRenderExecutionEvent(
IPortletExecutionEventFactory portalEventFactory,
PortletRendererImpl source,
HttpServletRequest request,
IPortletWindowId portletWindowId,
long executionTime,
boolean targeted,
boolean cached);
/** @return The {@link PortletRequest#RENDER_PART} name */
public final String getRenderPart() {
return renderPart;
}
}
protected PortletRenderResult doRender(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
PortletOutputHandler portletOutputHandler,
RenderPart renderPart)
throws IOException {
final CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState =
renderPart.getCacheState(
this.portletCacheControlService, httpServletRequest, portletWindowId);
final IPortletWindow portletWindow =
this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
/*
* If the portlet is rendering in EXCLUSIVE WindowState ignore the provided PortletOutputHandler and
* write directly to the response.
*
* THIS IS VERY BAD AND SHOULD BE DEPRECATED ALONG WITH EXCLUSIVE WINDOW STATE
*/
if (EXCLUSIVE.equals(portletWindow.getWindowState())) {
portletOutputHandler = new ResourcePortletOutputHandler(httpServletResponse);
}
if (cacheState.isUseCachedData()) {
return doRenderReplayCachedContent(
portletWindow,
httpServletRequest,
cacheState,
portletOutputHandler,
renderPart,
0);
}
final int cacheSizeThreshold = this.portletCacheControlService.getCacheSizeThreshold();
final CachingPortletOutputHandler cachingPortletOutputHandler =
new CachingPortletOutputHandler(portletOutputHandler, cacheSizeThreshold);
final CacheControl cacheControl = cacheState.getCacheControl();
//Setup the request and response
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse =
new PortletMimeHttpServletResponseWrapper(
httpServletResponse, portletWindow, portletOutputHandler, cacheControl);
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, cacheControl);
httpServletRequest.setAttribute(
ATTRIBUTE__PORTLET_OUTPUT_HANDLER, cachingPortletOutputHandler);
logger.debug("Rendering portlet {} for window {}", renderPart.name(), portletWindow);
final long renderStartTime = System.nanoTime();
try {
httpServletRequest.setAttribute(PortletRequest.RENDER_PART, renderPart.getRenderPart());
this.portletContainer.doRender(
portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
} catch (PortletException pe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing renderMarkup.",
portletWindow,
pe);
} catch (PortletContainerException pce) {
throw new PortletDispatchException(
"The portlet container threw an exception while executing renderMarkup on portlet window '"
+ portletWindow
+ "'.",
portletWindow,
pce);
} catch (IOException ioe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing renderMarkup.",
portletWindow,
ioe);
}
final long executionTime = System.nanoTime() - renderStartTime;
//See if the portlet signaled to use the cached content
final boolean useCachedContent = cacheControl.useCachedContent();
if (useCachedContent) {
final CachedPortletData<PortletRenderResult> cachedPortletData =
cacheState.getCachedPortletData();
if (cachedPortletData == null) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' indicated via CacheControl#useCachedContent "
+ "that the portal should render cached content, however there is no cached content to return. "
+ "This is a portlet bug.",
portletWindow);
}
//Update the expiration time and re-store in the cache
cachedPortletData.updateExpirationTime(cacheControl.getExpirationTime());
renderPart.cachePortletOutput(
portletCacheControlService,
portletWindowId,
httpServletRequest,
cacheState,
cachedPortletData);
return doRenderReplayCachedContent(
portletWindow,
httpServletRequest,
cacheState,
portletOutputHandler,
renderPart,
executionTime);
}
publishRenderEvent(portletWindow, httpServletRequest, renderPart, executionTime, false);
//Build the render result
final PortletRenderResult portletRenderResult =
constructPortletRenderResult(httpServletRequest, executionTime);
//Check if the portlet's output should be cached
if (cacheState != null) {
boolean shouldCache =
this.portletCacheControlService.shouldOutputBeCached(cacheControl);
if (shouldCache) {
final CachedPortletData<PortletRenderResult> cachedPortletData =
cachingPortletOutputHandler.getCachedPortletData(
portletRenderResult, cacheControl);
if (cachedPortletData != null) {
renderPart.cachePortletOutput(
portletCacheControlService,
portletWindowId,
httpServletRequest,
cacheState,
cachedPortletData);
}
}
}
return portletRenderResult;
}
/**
* Replay the cached content inside the {@link CachedPortletData} as the response to a doRender.
*/
protected PortletRenderResult doRenderReplayCachedContent(
IPortletWindow portletWindow,
HttpServletRequest httpServletRequest,
CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
PortletOutputHandler portletOutputHandler,
RenderPart renderPart,
long baseExecutionTime)
throws IOException {
enforceConfigPermission(httpServletRequest, portletWindow);
logger.debug(
"Replaying cached content for Render {} request to {}", renderPart, portletWindow);
final long renderStartTime = System.nanoTime();
final CachedPortletData<PortletRenderResult> cachedPortletData =
cacheState.getCachedPortletData();
cachedPortletData.replay(portletOutputHandler);
final long executionTime = baseExecutionTime + (System.nanoTime() - renderStartTime);
publishRenderEvent(portletWindow, httpServletRequest, renderPart, executionTime, true);
final PortletRenderResult portletResult = cachedPortletData.getPortletResult();
return new PortletRenderResult(portletResult, executionTime);
}
/** Publish the portlet render event */
protected void publishRenderEvent(
IPortletWindow portletWindow,
HttpServletRequest httpServletRequest,
RenderPart renderPart,
long executionTime,
boolean cached) {
final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
//Determine if the portlet was targeted
final IPortalRequestInfo portalRequestInfo =
this.urlSyntaxProvider.getPortalRequestInfo(httpServletRequest);
final boolean targeted =
portletWindowId.equals(portalRequestInfo.getTargetedPortletWindowId());
renderPart.publishRenderExecutionEvent(
this.portalEventFactory,
this,
httpServletRequest,
portletWindowId,
executionTime,
targeted,
cached);
}
/**
* Construct a {@link PortletRenderResult} from information in the {@link HttpServletRequest}.
* The second argument is how long the render action took.
*
* @param httpServletRequest
* @param renderTime
* @return an appropriate {@link PortletRenderResult}, never null
*/
protected PortletRenderResult constructPortletRenderResult(
HttpServletRequest httpServletRequest, long renderTime) {
final String title =
(String) httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_TITLE);
final String newItemCountString =
(String)
httpServletRequest.getAttribute(
IPortletRenderer.ATTRIBUTE__PORTLET_NEW_ITEM_COUNT);
final int newItemCount;
if (newItemCountString != null && StringUtils.isNumeric(newItemCountString)) {
newItemCount = Integer.parseInt(newItemCountString);
} else {
newItemCount = 0;
}
final String link =
(String) httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_LINK);
return new PortletRenderResult(title, link, newItemCount, renderTime);
}
@Override
public long doServeResource(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
PortletResourceOutputHandler portletOutputHandler)
throws IOException {
final CacheState<CachedPortletResourceData<Long>, Long> cacheState =
this.portletCacheControlService.getPortletResourceState(
httpServletRequest, portletWindowId);
final IPortletWindow portletWindow =
this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
enforceConfigPermission(httpServletRequest, portletWindow);
if (cacheState.isUseBrowserData()) {
logger.trace("doServeResource-Reusing browser data");
return doResourceReplayBrowserContent(
portletWindow, httpServletRequest, cacheState, portletOutputHandler);
}
if (cacheState.isUseCachedData()) {
logger.trace("doServeResource-Reusing cached data");
return doResourceReplayCachedContent(
portletWindow, httpServletRequest, cacheState, portletOutputHandler, 0);
}
final int cacheSizeThreshold = this.portletCacheControlService.getCacheSizeThreshold();
final CachingPortletResourceOutputHandler cachingPortletOutputHandler =
new CachingPortletResourceOutputHandler(portletOutputHandler, cacheSizeThreshold);
CacheControl cacheControl = cacheState.getCacheControl();
//Wrap the cache control so it immediately sets the caching related response headers
cacheControl = new HeaderSettingCacheControl(cacheControl, cachingPortletOutputHandler);
//Setup the request and response
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse =
new PortletResourceHttpServletResponseWrapper(
httpServletResponse, portletWindow, portletOutputHandler, cacheControl);
httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, cacheControl);
httpServletRequest.setAttribute(
ATTRIBUTE__PORTLET_OUTPUT_HANDLER, cachingPortletOutputHandler);
this.logger.debug("Executing resource request for window {}", portletWindow);
final long start = System.nanoTime();
try {
this.portletContainer.doServeResource(
portletWindow.getPlutoPortletWindow(), httpServletRequest, httpServletResponse);
} catch (PortletException pe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing serveResource.",
portletWindow,
pe);
} catch (PortletContainerException pce) {
throw new PortletDispatchException(
"The portlet container threw an exception while executing serveResource on portlet window '"
+ portletWindow
+ "'.",
portletWindow,
pce);
} catch (IOException ioe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing serveResource.",
portletWindow,
ioe);
}
final long executionTime = System.nanoTime() - start;
//See if the portlet signaled to use the cached content
final boolean useCachedContent = cacheControl.useCachedContent();
if (useCachedContent) {
final CachedPortletResourceData<Long> cachedPortletResourceData =
cacheState.getCachedPortletData();
if (cachedPortletResourceData != null) {
//Update the expiration time and re-store in the cache
final CachedPortletData<Long> cachedPortletData =
cachedPortletResourceData.getCachedPortletData();
cachedPortletData.updateExpirationTime(cacheControl.getExpirationTime());
this.portletCacheControlService.cachePortletResourceOutput(
portletWindowId, httpServletRequest, cacheState, cachedPortletResourceData);
}
if (cacheState.isBrowserSetEtag()) {
logger.trace("doServeResource-useCachedContent, Reusing browser data");
//Browser-side content matches, send a 304
return doResourceReplayBrowserContent(
portletWindow, httpServletRequest, cacheState, portletOutputHandler);
}
logger.trace("doServeResource-useCachedContent, Reusing cached data");
return doResourceReplayCachedContent(
portletWindow,
httpServletRequest,
cacheState,
cachingPortletOutputHandler,
executionTime);
}
publishResourceEvent(portletWindow, httpServletRequest, executionTime, false, false);
if (cacheState != null) {
boolean shouldCache =
this.portletCacheControlService.shouldOutputBeCached(cacheControl);
if (shouldCache) {
final CachedPortletResourceData<Long> cachedPortletResourceData =
cachingPortletOutputHandler.getCachedPortletResourceData(
executionTime, cacheControl);
if (cachedPortletResourceData != null) {
this.portletCacheControlService.cachePortletResourceOutput(
portletWindowId,
httpServletRequest,
cacheState,
cachedPortletResourceData);
}
}
}
return executionTime;
}
protected long doResourceReplayBrowserContent(
IPortletWindow portletWindow,
HttpServletRequest httpServletRequest,
CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler) {
enforceConfigPermission(httpServletRequest, portletWindow);
logger.debug("Sending 304 for resource request to {}", portletWindow);
if (portletOutputHandler.isCommitted()) {
throw new IllegalStateException(
"Attempting to send 304 but response is already committed");
}
final long start = System.nanoTime();
portletOutputHandler.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
final CachedPortletResourceData<Long> cachedPortletResourceData =
cacheState.getCachedPortletData();
if (cachedPortletResourceData != null) {
//Freshen up the various caching related headers
final CachedPortletData<Long> cachedPortletData =
cachedPortletResourceData.getCachedPortletData();
PortletCachingHeaderUtils.setCachingHeaders(cachedPortletData, portletOutputHandler);
}
final long executionTime = System.nanoTime() - start;
publishResourceEvent(portletWindow, httpServletRequest, executionTime, true, false);
return executionTime;
}
/**
* Replay the cached content inside the {@link CachedPortletData} as the response to a
* doResource.
*/
protected Long doResourceReplayCachedContent(
IPortletWindow portletWindow,
HttpServletRequest httpServletRequest,
CacheState<CachedPortletResourceData<Long>, Long> cacheState,
PortletResourceOutputHandler portletOutputHandler,
long baseExecutionTime)
throws IOException {
enforceConfigPermission(httpServletRequest, portletWindow);
logger.debug("Replaying cached content for resource request to {}", portletWindow);
final long renderStartTime = System.nanoTime();
final CachedPortletResourceData<Long> cachedPortletResourceData =
cacheState.getCachedPortletData();
if (cachedPortletResourceData == null) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' indicated via CacheControl#useCachedContent "
+ "that the portal should render cached content, however there is no cached content to return. "
+ "This is a portlet bug.",
portletWindow);
}
cachedPortletResourceData.replay(portletOutputHandler);
final long executionTime = baseExecutionTime + (System.nanoTime() - renderStartTime);
publishResourceEvent(portletWindow, httpServletRequest, executionTime, false, true);
return executionTime;
}
/** Publish the portlet resource event */
protected void publishResourceEvent(
IPortletWindow portletWindow,
HttpServletRequest httpServletRequest,
long executionTime,
boolean usedBrowserCache,
boolean usedPortalCache) {
final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
this.portalEventFactory.publishPortletResourceExecutionEvent(
httpServletRequest,
this,
portletWindowId,
executionTime,
usedBrowserCache,
usedPortalCache);
}
/*
* (non-Javadoc)
* @see org.apereo.portal.portlet.rendering.IPortletRenderer#doReset(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public void doReset(
IPortletWindowId portletWindowId,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
// Don't enforce config mode restriction because this is going to snap the portlet window back into view mode.
final IPortletWindow portletWindow =
this.portletWindowRegistry.getPortletWindow(httpServletRequest, portletWindowId);
if (portletWindow != null) {
portletWindow.setPortletMode(PortletMode.VIEW);
portletWindow.setRenderParameters(new ParameterMap());
portletWindow.setExpirationCache(null);
httpServletRequest = this.setupPortletRequest(httpServletRequest);
httpServletResponse =
new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);
httpServletRequest.setAttribute(
AdministrativeRequestListenerController.DEFAULT_LISTENER_KEY_ATTRIBUTE,
"sessionActionListener");
httpServletRequest.setAttribute(
PortletSessionAdministrativeRequestListener.ACTION,
PortletSessionAdministrativeRequestListener.SessionAction.CLEAR);
httpServletRequest.setAttribute(
PortletSessionAdministrativeRequestListener.SCOPE,
PortletSession.PORTLET_SCOPE);
//TODO modify PortletContainer.doAdmin to create a specific "admin" req/res object and context so we don't have to fake it with a render req
//These are required for a render request to be created and admin requests use a render request under the hood
final String characterEncoding = httpServletResponse.getCharacterEncoding();
httpServletRequest.setAttribute(
ATTRIBUTE__PORTLET_OUTPUT_HANDLER,
new RenderPortletOutputHandler(characterEncoding));
httpServletRequest.setAttribute(
ATTRIBUTE__PORTLET_CACHE_CONTROL, new CacheControlImpl());
try {
this.portletContainer.doAdmin(
portletWindow.getPlutoPortletWindow(),
httpServletRequest,
httpServletResponse);
} catch (PortletException pe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing admin command to clear session.",
portletWindow,
pe);
} catch (PortletContainerException pce) {
throw new PortletDispatchException(
"The portlet container threw an exception while executing admin command to clear session on portlet window '"
+ portletWindow
+ "'.",
portletWindow,
pce);
} catch (IOException ioe) {
throw new PortletDispatchException(
"The portlet window '"
+ portletWindow
+ "' threw an exception while executing admin command to clear session.",
portletWindow,
ioe);
}
} else {
logger.debug(
"ignoring doReset as portletWindowRegistry#getPortletWindow returned a null result for portletWindowId {}",
portletWindowId);
}
}
protected HttpServletRequest setupPortletRequest(HttpServletRequest httpServletRequest) {
final PortletHttpServletRequestWrapper portletHttpServletRequestWrapper =
new PortletHttpServletRequestWrapper(httpServletRequest);
portletHttpServletRequestWrapper.setAttribute(
PortletDelegationLocator.PORTLET_DELECATION_LOCATOR_ATTR,
this.portletDelegationLocator);
return portletHttpServletRequestWrapper;
}
/**
* Enforces config mode access control. If requesting user does not have CONFIG permission, and
* the PortletWindow specifies config mode, throws AuthorizationException. Otherwise does
* nothing.
*
* @param httpServletRequest the non-null current HttpServletRequest (for determining requesting
* user)
* @param portletWindow a non-null portlet window that might be in config mode
* @throws AuthorizationException if the user is not permitted to access config mode yet portlet
* window specifies config mode
* @throws java.lang.IllegalArgumentException if the request or window are null
* @since 4.0.13.1, 4.0.14, 4.1.
*/
protected void enforceConfigPermission(
final HttpServletRequest httpServletRequest, final IPortletWindow portletWindow) {
Validate.notNull(
httpServletRequest, "Servlet request must not be null to determine remote user.");
Validate.notNull(portletWindow, "Portlet window must not be null to determine its mode.");
final PortletMode portletMode = portletWindow.getPortletMode();
if (portletMode != null) {
if (IPortletRenderer.CONFIG.equals(portletMode)) {
final IPerson person = this.personManager.getPerson(httpServletRequest);
final EntityIdentifier ei = person.getEntityIdentifier();
final AuthorizationService authorizationService = AuthorizationService.instance();
final IAuthorizationPrincipal ap =
authorizationService.newPrincipal(ei.getKey(), ei.getType());
final IPortletEntity portletEntity = portletWindow.getPortletEntity();
final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
if (!ap.canConfigure(portletDefinition.getPortletDefinitionId().getStringId())) {
logger.error(
"User {} attempted to use portlet {} in {} but lacks permission to use that mode. "
+ "THIS MAY BE AN ATTEMPT TO EXPLOIT A HISTORICAL SECURITY FLAW. "
+ "You should probably figure out who this user is and why they are trying to access "
+ "unauthorized portlet modes.",
person.getUserName(),
portletDefinition.getFName(),
portletMode);
throw new AuthorizationException(
person.getUserName()
+ " does not have permission to render '"
+ portletDefinition.getFName()
+ "' in "
+ portletMode
+ " PortletMode.");
}
}
}
}
}