/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.layoutconfiguration.util;
import com.liferay.portal.kernel.executor.PortalExecutorManagerUtil;
import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
import com.liferay.portal.kernel.layoutconfiguration.util.RuntimePage;
import com.liferay.portal.kernel.layoutconfiguration.util.xml.RuntimeLogic;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.LayoutTemplate;
import com.liferay.portal.kernel.model.LayoutTemplateConstants;
import com.liferay.portal.kernel.model.Portlet;
import com.liferay.portal.kernel.security.pacl.DoPrivileged;
import com.liferay.portal.kernel.service.LayoutTemplateLocalServiceUtil;
import com.liferay.portal.kernel.servlet.PluginContextListener;
import com.liferay.portal.kernel.servlet.ServletContextPool;
import com.liferay.portal.kernel.template.Template;
import com.liferay.portal.kernel.template.TemplateConstants;
import com.liferay.portal.kernel.template.TemplateManager;
import com.liferay.portal.kernel.template.TemplateManagerUtil;
import com.liferay.portal.kernel.template.TemplateResource;
import com.liferay.portal.kernel.util.ClassLoaderUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.JavaConstants;
import com.liferay.portal.kernel.util.ObjectValuePair;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferay.portal.layoutconfiguration.util.velocity.CustomizationSettingsProcessor;
import com.liferay.portal.layoutconfiguration.util.velocity.TemplateProcessor;
import com.liferay.portal.layoutconfiguration.util.xml.ActionURLLogic;
import com.liferay.portal.layoutconfiguration.util.xml.PortletLogic;
import com.liferay.portal.layoutconfiguration.util.xml.RenderURLLogic;
import com.liferay.portal.servlet.ThreadLocalFacadeServletRequestWrapperUtil;
import com.liferay.portal.util.PropsValues;
import com.liferay.taglib.servlet.PipingServletResponse;
import com.liferay.taglib.util.DummyVelocityTaglib;
import com.liferay.taglib.util.VelocityTaglib;
import java.io.Closeable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.portlet.PortletResponse;
import javax.portlet.RenderResponse;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.time.StopWatch;
/**
* @author Brian Wing Shun Chan
* @author Raymond Augé
* @author Shuyang Zhou
*/
@DoPrivileged
public class RuntimePageImpl implements RuntimePage {
@Override
public StringBundler getProcessedTemplate(
HttpServletRequest request, HttpServletResponse response,
String portletId, TemplateResource templateResource)
throws Exception {
return doDispatch(request, response, portletId, templateResource, true);
}
@Override
public void processCustomizationSettings(
HttpServletRequest request, HttpServletResponse response,
TemplateResource templateResource)
throws Exception {
doDispatch(request, response, null, templateResource, false);
}
@Override
public void processTemplate(
HttpServletRequest request, HttpServletResponse response,
String portletId, TemplateResource templateResource)
throws Exception {
StringBundler sb = doDispatch(
request, response, portletId, templateResource, true);
sb.writeTo(response.getWriter());
}
@Override
public void processTemplate(
HttpServletRequest request, HttpServletResponse response,
TemplateResource templateResource)
throws Exception {
processTemplate(request, response, null, templateResource);
}
@Override
public String processXML(
HttpServletRequest request, HttpServletResponse response,
String content)
throws Exception {
PortletResponse portletResponse = (PortletResponse)request.getAttribute(
JavaConstants.JAVAX_PORTLET_RESPONSE);
if ((portletResponse != null) &&
!(portletResponse instanceof RenderResponse)) {
throw new IllegalArgumentException(
"processXML can only be invoked in the render phase");
}
RuntimeLogic portletLogic = new PortletLogic(request, response);
content = processXML(request, content, portletLogic);
if (portletResponse == null) {
return content;
}
RenderResponse renderResponse = (RenderResponse)portletResponse;
RuntimeLogic actionURLLogic = new ActionURLLogic(renderResponse);
RuntimeLogic renderURLLogic = new RenderURLLogic(renderResponse);
content = processXML(request, content, actionURLLogic);
content = processXML(request, content, renderURLLogic);
return content;
}
@Override
public String processXML(
HttpServletRequest request, String content,
RuntimeLogic runtimeLogic)
throws Exception {
if (Validator.isNull(content)) {
return StringPool.BLANK;
}
int index = content.indexOf(runtimeLogic.getOpenTag());
if (index == -1) {
return content;
}
Portlet renderPortlet = (Portlet)request.getAttribute(
WebKeys.RENDER_PORTLET);
Boolean renderPortletResource = (Boolean)request.getAttribute(
WebKeys.RENDER_PORTLET_RESOURCE);
String outerPortletId = (String)request.getAttribute(
WebKeys.OUTER_PORTLET_ID);
if (outerPortletId == null) {
request.setAttribute(
WebKeys.OUTER_PORTLET_ID, renderPortlet.getPortletId());
}
try {
request.setAttribute(WebKeys.RENDER_PORTLET_RESOURCE, Boolean.TRUE);
StringBundler sb = new StringBundler();
int x = 0;
int y = index;
while (y != -1) {
sb.append(content.substring(x, y));
int close1 = content.indexOf(runtimeLogic.getClose1Tag(), y);
int close2 = content.indexOf(runtimeLogic.getClose2Tag(), y);
if ((close2 == -1) || ((close1 != -1) && (close1 < close2))) {
x = close1 + runtimeLogic.getClose1Tag().length();
}
else {
x = close2 + runtimeLogic.getClose2Tag().length();
}
String runtimePortletTag = content.substring(y, x);
if ((renderPortlet != null) &&
runtimePortletTag.contains(renderPortlet.getPortletId())) {
return StringPool.BLANK;
}
sb.append(runtimeLogic.processXML(runtimePortletTag));
y = content.indexOf(runtimeLogic.getOpenTag(), x);
}
if (y == -1) {
sb.append(content.substring(x));
}
return sb.toString();
}
finally {
if (outerPortletId == null) {
request.removeAttribute(WebKeys.OUTER_PORTLET_ID);
}
request.setAttribute(WebKeys.RENDER_PORTLET, renderPortlet);
if (renderPortletResource == null) {
request.removeAttribute(WebKeys.RENDER_PORTLET_RESOURCE);
}
else {
request.setAttribute(
WebKeys.RENDER_PORTLET_RESOURCE, renderPortletResource);
}
}
}
protected StringBundler doDispatch(
HttpServletRequest request, HttpServletResponse response,
String portletId, TemplateResource templateResource,
boolean processTemplate)
throws Exception {
ClassLoader pluginClassLoader = null;
LayoutTemplate layoutTemplate = getLayoutTemplate(
templateResource.getTemplateId());
if (layoutTemplate != null) {
String pluginServletContextName = GetterUtil.getString(
layoutTemplate.getServletContextName());
ServletContext pluginServletContext = ServletContextPool.get(
pluginServletContextName);
if (pluginServletContext != null) {
pluginClassLoader =
(ClassLoader)pluginServletContext.getAttribute(
PluginContextListener.PLUGIN_CLASS_LOADER);
}
}
ClassLoader contextClassLoader =
ClassLoaderUtil.getContextClassLoader();
try {
if ((pluginClassLoader != null) &&
(pluginClassLoader != contextClassLoader)) {
ClassLoaderUtil.setContextClassLoader(pluginClassLoader);
}
if (processTemplate) {
return doProcessTemplate(
request, response, portletId, templateResource, false);
}
else {
doProcessCustomizationSettings(
request, response, templateResource, false);
return null;
}
}
finally {
if ((pluginClassLoader != null) &&
(pluginClassLoader != contextClassLoader)) {
ClassLoaderUtil.setContextClassLoader(contextClassLoader);
}
}
}
protected void doProcessCustomizationSettings(
HttpServletRequest request, HttpServletResponse response,
TemplateResource templateResource, boolean restricted)
throws Exception {
CustomizationSettingsProcessor processor =
new CustomizationSettingsProcessor(request, response);
Template template = TemplateManagerUtil.getTemplate(
TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
template.put("processor", processor);
// Velocity variables
template.prepare(request);
// liferay:include tag library
VelocityTaglib velocityTaglib = new DummyVelocityTaglib();
template.put("taglibLiferay", velocityTaglib);
template.put("theme", velocityTaglib);
try {
template.processTemplate(response.getWriter());
}
catch (Exception e) {
_log.error(e, e);
throw e;
}
}
protected StringBundler doProcessTemplate(
HttpServletRequest request, HttpServletResponse response,
String portletId, TemplateResource templateResource,
boolean restricted)
throws Exception {
TemplateProcessor processor = new TemplateProcessor(
request, response, portletId);
TemplateManager templateManager =
TemplateManagerUtil.getTemplateManager(
TemplateConstants.LANG_TYPE_VM);
Template template = TemplateManagerUtil.getTemplate(
TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
template.put("processor", processor);
// Velocity variables
template.prepare(request);
UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
templateManager.addTaglibTheme(
template, "taglibLiferay", request,
new PipingServletResponse(response, unsyncStringWriter));
try {
template.processTemplate(unsyncStringWriter);
}
catch (Exception e) {
_log.error(e, e);
throw e;
}
boolean portletParallelRender = GetterUtil.getBoolean(
request.getAttribute(WebKeys.PORTLET_PARALLEL_RENDER));
Lock lock = null;
Map<String, StringBundler> contentsMap = new HashMap<>();
Map<Integer, List<PortletRenderer>> portletRenderersMap =
processor.getPortletRenderers();
for (Map.Entry<Integer, List<PortletRenderer>> entry :
portletRenderersMap.entrySet()) {
if (_log.isDebugEnabled()) {
_log.debug(
"Processing portlets with render weight " + entry.getKey());
}
List<PortletRenderer> portletRenderers = entry.getValue();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
if (portletParallelRender && (portletRenderers.size() > 1)) {
if (_log.isDebugEnabled()) {
_log.debug("Start parallel rendering");
}
if (lock == null) {
lock = new ReentrantLock();
}
request.setAttribute(
WebKeys.PARALLEL_RENDERING_MERGE_LOCK, lock);
ObjectValuePair<HttpServletRequest, Closeable> objectValuePair =
ThreadLocalFacadeServletRequestWrapperUtil.inject(request);
try {
parallelyRenderPortlets(
objectValuePair.getKey(), response, processor,
contentsMap, portletRenderers);
}
finally {
Closeable closeable = objectValuePair.getValue();
closeable.close();
}
request.removeAttribute(WebKeys.PARALLEL_RENDERING_MERGE_LOCK);
if (_log.isDebugEnabled()) {
_log.debug(
"Finished parallel rendering in " +
stopWatch.getTime() + " ms");
}
}
else {
if (_log.isDebugEnabled()) {
_log.debug("Start serial rendering");
}
for (PortletRenderer portletRenderer : portletRenderers) {
Portlet portlet = portletRenderer.getPortlet();
contentsMap.put(
portlet.getPortletId(),
portletRenderer.render(request, response));
if (_log.isDebugEnabled()) {
_log.debug(
"Serially rendered portlet " +
portlet.getPortletId() + " in " +
stopWatch.getTime() + " ms");
}
}
if (_log.isDebugEnabled()) {
_log.debug(
"Finished serial rendering in " + stopWatch.getTime() +
" ms");
}
}
}
if (portletParallelRender && (_waitTime == Integer.MAX_VALUE)) {
_waitTime = PropsValues.LAYOUT_PARALLEL_RENDER_TIMEOUT;
}
StringBundler sb = StringUtil.replaceWithStringBundler(
unsyncStringWriter.toString(), "[$TEMPLATE_PORTLET_", "$]",
contentsMap);
return sb;
}
protected LayoutTemplate getLayoutTemplate(String velocityTemplateId) {
String separator = LayoutTemplateConstants.CUSTOM_SEPARATOR;
boolean standard = false;
if (velocityTemplateId.contains(
LayoutTemplateConstants.STANDARD_SEPARATOR)) {
separator = LayoutTemplateConstants.STANDARD_SEPARATOR;
standard = true;
}
String layoutTemplateId = null;
String themeId = null;
int pos = velocityTemplateId.indexOf(separator);
if (pos != -1) {
layoutTemplateId = velocityTemplateId.substring(
pos + separator.length());
themeId = velocityTemplateId.substring(0, pos);
}
pos = layoutTemplateId.indexOf(
LayoutTemplateConstants.INSTANCE_SEPARATOR);
if (pos != -1) {
layoutTemplateId = layoutTemplateId.substring(
pos + LayoutTemplateConstants.INSTANCE_SEPARATOR.length() + 1);
pos = layoutTemplateId.indexOf(StringPool.UNDERLINE);
layoutTemplateId = layoutTemplateId.substring(pos + 1);
}
return LayoutTemplateLocalServiceUtil.getLayoutTemplate(
layoutTemplateId, standard, themeId);
}
protected void parallelyRenderPortlets(
HttpServletRequest request, HttpServletResponse response,
TemplateProcessor processor, Map<String, StringBundler> contentsMap,
List<PortletRenderer> portletRenderers)
throws Exception {
ExecutorService executorService =
PortalExecutorManagerUtil.getPortalExecutor(
RuntimePageImpl.class.getName());
Map<Future<StringBundler>, PortletRenderer> futures = new HashMap<>(
portletRenderers.size());
for (PortletRenderer portletRenderer : portletRenderers) {
if (_log.isDebugEnabled()) {
Portlet portlet = portletRenderer.getPortlet();
_log.debug(
"Submit portlet " + portlet.getPortletId() +
" for parallel rendering");
}
Callable<StringBundler> renderCallable =
portletRenderer.getCallable(request, response);
Future<StringBundler> future = null;
try {
future = executorService.submit(renderCallable);
}
catch (RejectedExecutionException ree) {
// This should only happen when user configures an AbortPolicy
// (or some other customized RejectedExecutionHandler that
// throws RejectedExecutionException) for this
// ThreadPoolExecutor. AbortPolicy is not the recommended
// setting, but to be more robust, we take care of this by
// converting the rejection to a fallback action.
future = new FutureTask<>(renderCallable);
// Cancel immediately
future.cancel(true);
}
futures.put(future, portletRenderer);
}
long waitTime = _waitTime;
for (Map.Entry<Future<StringBundler>, PortletRenderer> entry :
futures.entrySet()) {
Future<StringBundler> future = entry.getKey();
PortletRenderer portletRenderer = entry.getValue();
Portlet portlet = portletRenderer.getPortlet();
if (future.isCancelled()) {
if (_log.isDebugEnabled()) {
_log.debug(
"Reject portlet " + portlet.getPortletId() +
" for parallel rendering");
}
}
else if ((waitTime > 0) || future.isDone()) {
try {
long startTime = System.currentTimeMillis();
StringBundler sb = future.get(
waitTime, TimeUnit.MILLISECONDS);
long duration = System.currentTimeMillis() - startTime;
waitTime -= duration;
contentsMap.put(portlet.getPortletId(), sb);
if (_log.isDebugEnabled()) {
_log.debug(
"Parallely rendered portlet " +
portlet.getPortletId() + " in " + duration +
" ms");
}
continue;
}
catch (ExecutionException ee) {
throw ee;
}
catch (InterruptedException ie) {
// On interruption, stop waiting, force all pending portlets
// to fall back to ajax loading or an error message.
waitTime = -1;
}
catch (TimeoutException te) {
// On timeout, stop waiting, force all pending portlets to
// fall back to ajax loading or an error message.
waitTime = -1;
}
catch (CancellationException ce) {
// This should only happen on a concurrent shutdown of the
// thread pool. Simply stops the render process.
if (_log.isDebugEnabled()) {
_log.debug(
"Asynchronized cancellation detected that should " +
"only be caused by a concurrent shutdown of " +
"the thread pool",
ce);
}
return;
}
// Cancel by interrupting rendering thread
future.cancel(true);
}
StringBundler sb = null;
if (processor.isPortletAjaxRender() && portlet.isAjaxable()) {
if (_log.isDebugEnabled()) {
_log.debug(
"Fall back to ajax rendering of portlet " +
portlet.getPortletId());
}
sb = portletRenderer.renderAjax(request, response);
}
else {
if (_log.isDebugEnabled()) {
if (processor.isPortletAjaxRender()) {
_log.debug(
"Fall back to an error message for portlet " +
portlet.getPortletId() +
" since it is not ajaxable");
}
else {
_log.debug(
"Fall back to an error message for portlet " +
portlet.getPortletId() +
" since ajax rendering is disabled");
}
}
sb = portletRenderer.renderError(request, response);
}
contentsMap.put(portlet.getPortletId(), sb);
}
for (PortletRenderer portletRender : portletRenderers) {
portletRender.finishParallelRender();
}
}
private static final Log _log = LogFactoryUtil.getLog(
RuntimePageImpl.class);
private int _waitTime = Integer.MAX_VALUE;
}