/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/portal/trunk/portal-render-impl/impl/src/java/org/sakaiproject/portal/render/portlet/PortletToolRenderService.java $
* $Id: PortletToolRenderService.java 109718 2012-06-27 00:19:29Z csev@umich.edu $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community 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
*
* http://www.opensource.org/licenses/ECL-2.0
*
* 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.sakaiproject.portal.render.portlet;
import java.io.IOException;
import java.io.Writer;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.PortletContainer;
import org.apache.pluto.PortletContainerException;
import org.apache.pluto.PortletContainerFactory;
import org.apache.pluto.RequiredContainerServices;
import org.apache.pluto.core.PortletContextManager;
import org.apache.pluto.descriptors.portlet.PortletDD;
import org.apache.pluto.descriptors.portlet.SupportsDD;
import org.apache.pluto.spi.PortalCallbackService;
import org.apache.pluto.spi.PortletURLProvider;
import org.sakaiproject.util.Web;
import org.sakaiproject.util.FormattedText;
import org.sakaiproject.portal.api.Portal;
import org.sakaiproject.portal.api.PortalService;
import org.sakaiproject.portal.util.BufferedServletResponse;
import org.sakaiproject.portal.render.api.RenderResult;
import org.sakaiproject.portal.render.api.ToolRenderException;
import org.sakaiproject.portal.render.api.ToolRenderService;
import org.sakaiproject.portal.render.portlet.services.SakaiOptionalPortletContainerServices;
import org.sakaiproject.portal.render.portlet.services.SakaiPortalCallbackService;
import org.sakaiproject.portal.render.portlet.services.SakaiPortalContext;
import org.sakaiproject.portal.render.portlet.services.SakaiPortletContainerServices;
import org.sakaiproject.portal.render.portlet.services.state.PortletState;
import org.sakaiproject.portal.render.portlet.services.state.PortletStateAccess;
import org.sakaiproject.portal.render.portlet.services.state.PortletStateEncoder;
import org.sakaiproject.portal.render.portlet.servlet.SakaiServletActionRequest;
import org.sakaiproject.portal.render.portlet.servlet.SakaiServletRequest;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.tool.api.Placement;
import org.w3c.tidy.Tidy;
/**
* @author ddwolf
* @author ieb
* @author csev
* @since Sakai 2.4
* @version $Rev: 109718 $
*/
public class PortletToolRenderService implements ToolRenderService
{
/**
* Log instance used for all instances of this service.
*/
private static final Log LOG = LogFactory.getLog(PortletToolRenderService.class);
/**
* Portlet Container instance used by this service.
*/
private PortletContainer container;
/**
* Portlet Registry used by this service.
*/
private PortletRegistry registry = new PortletRegistry();
private PortletStateEncoder portletStateEncoder;
private PortalService portalService;
public PortletStateEncoder getPortletStateEncoder()
{
return portletStateEncoder;
}
public void setPortletStateEncoder(PortletStateEncoder portletStateEncoder)
{
this.portletStateEncoder = portletStateEncoder;
}
public boolean preprocess(Portal portal, HttpServletRequest request, HttpServletResponse response,
ServletContext context) throws IOException
{
String stateParam = request
.getParameter(SakaiPortalCallbackService.PORTLET_STATE_QUERY_PARAM);
// If there is not state parameter, short circuit
if (stateParam == null)
{
return true;
}
PortletState state = portletStateEncoder.decode(stateParam);
PortletStateAccess.setPortletState(request, state);
if (LOG.isDebugEnabled())
{
LOG.debug("New Portlet State retrieved for Tool '" + state.getId() + ".");
}
if (state.isAction())
{
if (LOG.isDebugEnabled())
{
LOG.debug("Processing action for placement id " + state.getId());
}
PortletStateAccess.setPortletState(request, state);
SakaiPortletWindow window = isIn168TestMode(request) ? createPortletWindow(state
.getId())
: registry.getPortletWindow(state.getId());
window.setState(state);
try
{
PortletContainer portletContainer = getPortletContainer(context);
portletContainer.doAction(window, new SakaiServletActionRequest(request,
state), response);
}
catch (PortletException e)
{
throw new ToolRenderException(e.getMessage(), e);
}
catch (PortletContainerException e)
{
throw new ToolRenderException(e.getMessage(), e);
}
finally
{
state.setAction(false);
}
return true;
}
return true;
}
// Note ToolConfiguration extends Placement
public RenderResult render(Portal portal, ToolConfiguration toolConfiguration,
final HttpServletRequest request, final HttpServletResponse response,
ServletContext context) throws IOException
{
getPortletDD(toolConfiguration);
final SakaiPortletWindow window = isIn168TestMode(request) ? createPortletWindow(toolConfiguration
.getId())
: registry.getOrCreatePortletWindow(toolConfiguration);
PortletState state = PortletStateAccess.getPortletState(request, window.getId()
.getStringId());
if (LOG.isDebugEnabled())
{
LOG.debug("Retrieved PortletState from request cache. Applying to window.");
}
if (portalService.isResetRequested(request))
{
if (state != null)
{
String statePrefix = "javax.portlet.p." + state.getId();
HttpSession session = request.getSession(true);
for (Enumeration e = session.getAttributeNames(); e.hasMoreElements();)
{
String key = (String) e.nextElement();
if (key != null && key.startsWith(statePrefix))
{
session.removeAttribute(key);
}
}
state = null; // Remove the remaining evidence of prior
// existence
}
}
if (state == null)
{
state = new PortletState(window.getId().getStringId());
PortletStateAccess.setPortletState(request, state);
}
window.setState(state);
try
{
final HttpServletRequest req = new SakaiServletRequest(request, state);
final PortletContainer portletContainer = getPortletContainer(context);
// Derive the Edit and Help URLs
String editUrl = null;
String helpUrl = null;
RequiredContainerServices rs = portletContainer
.getRequiredContainerServices();
PortalCallbackService pcs = rs.getPortalCallbackService();
PortletURLProvider pup = null;
if (isPortletModeAllowed(toolConfiguration, "edit"))
{
pup = pcs.getPortletURLProvider(request, window);
// System.out.println("pup = "+pup);
pup.setPortletMode(new PortletMode("edit"));
// System.out.println("pup edit="+pup.toString());
editUrl = pup.toString();
}
if (isPortletModeAllowed(toolConfiguration, "help"))
{
pup = pcs.getPortletURLProvider(request, window);
pup.setPortletMode(new PortletMode("help"));
// System.out.println("pup help="+pup.toString());
helpUrl = pup.toString();
}
return new Sakai168RenderResult(req, response, portletContainer, window,
helpUrl, editUrl);
}
catch (PortletContainerException e)
{
throw new ToolRenderException(e.getMessage(), e);
}
}
private class Sakai168RenderResult implements RenderResult
{
private HttpServletRequest req = null;
private HttpServletResponse response = null;
private PortletContainer portletContainer = null;
private String storedContent = null;
private BufferedServletResponse bufferedResponse = null;
private Exception bufferedException = null;
private SakaiPortletWindow window = null;
private String helpUrl = null;
private String editUrl = null;
public Sakai168RenderResult(HttpServletRequest req, HttpServletResponse response,
PortletContainer pc, SakaiPortletWindow window, String helpUrl,
String editUrl)
{
this.req = req;
this.response = response;
this.portletContainer = pc;
this.window = window;
this.helpUrl = helpUrl;
this.editUrl = editUrl;
}
private void renderResponse() throws ToolRenderException
{
if (bufferedResponse == null)
{
bufferedResponse = new BufferedServletResponse(response);
try
{
portletContainer.doRender(window, req, bufferedResponse);
}
catch (PortletException e)
{
throw new ToolRenderException(e.getMessage(), e);
}
catch (IOException e)
{
throw new ToolRenderException(e.getMessage(), e);
}
catch (PortletContainerException e)
{
throw new ToolRenderException(e.getMessage(), e);
}
}
}
/**
* In the pre-render case the portal calls getContent then getTitle then getContent again
* In the deprecated post-render case, the portal calls getTitle then getContent
*/
public String getContent() throws ToolRenderException
{
if ( storedContent != null ) return storedContent;
if ( bufferedException != null )
{
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
bufferedException.printStackTrace(printWriter);
String storedContent = "<div class=\"portlettraceback\">\n" +
FormattedText.escapeHtml(result.toString(),true) +
"\n</pre>\n";
return storedContent;
}
try
{
renderResponse();
storedContent = bufferedResponse.getInternalBuffer().getBuffer().toString();
// SAK-22335 - Tidy in BodyOnly mode eats script tags so it is advisory-only and off by default
// if ( ! "true".equals(ServerConfigurationService.getString("portal.portlet.tidy", "false")) ) return storedContent;
if ( ! "true".equals(ServerConfigurationService.getString("portal.portlet.tidy.warnings", "false")) ) return storedContent;
Tidy tidy = new Tidy();
tidy.setIndentContent(true);
tidy.setSmartIndent(true);
tidy.setPrintBodyOnly(true);
tidy.setTidyMark(false);
tidy.setDocType("loose");
// if ( ! "true".equals(ServerConfigurationService.getString("portal.portlet.tidy.warnings", "false")) )
// {
// tidy.setQuiet(true);
// tidy.setShowWarnings(false);
// }
StringReader is = new StringReader(storedContent);
StringWriter os = new StringWriter();
tidy.parse(is,os); // parse() throws no errors
// String tidyOutput = os.toString();
// if ( tidyOutput != null && tidyOutput.length() > 0 ) storedContent = tidyOutput;
return storedContent;
}
catch(ToolRenderException e)
{
bufferedException = e;
throw e;
}
}
public void setContent(String content)
{
storedContent = content;
}
public String getTitle() throws ToolRenderException
{
if ( bufferedException == null ) {
try
{
renderResponse();
return Web.escapeHtml(PortletStateAccess.getPortletState(req, window.getId().getStringId())
.getTitle());
}
catch(Exception e)
{
bufferedException = e;
}
}
return bufferedException.toString();
}
public String getJSR168EditUrl()
{
return this.editUrl;
}
public String getJSR168HelpUrl()
{
return this.helpUrl;
}
/* (non-Javadoc)
* @see org.sakaiproject.portal.render.api.RenderResult#getHead()
*/
public String getHead()
{
return "";
}
};
// TODO: This must be test code and needs removing
private SakaiPortletWindow createPortletWindow(String windowId)
{
String contextPath = "/rsf";
String portletName = "numberguess";
return new SakaiPortletWindow(windowId, contextPath, portletName);
}
private PortletContainer getPortletContainer(ServletContext context)
throws PortletContainerException
{
if (container == null)
{
container = createPortletContainer();
container.init(context);
}
return container;
}
private PortletContainer createPortletContainer() throws PortletContainerException
{
SakaiPortletContainerServices services = new SakaiPortletContainerServices();
SakaiOptionalPortletContainerServices optServices = new SakaiOptionalPortletContainerServices();
services.setPortalCallbackService(new SakaiPortalCallbackService());
services.setPortalContext(new SakaiPortalContext());
return PortletContainerFactory.getInstance().createContainer("sakai", services,
optServices);
}
private static boolean isIn168TestMode(HttpServletRequest request)
{
if ( ! "true".equals(ServerConfigurationService.getString("portal.allow.test168", null)) )
{
return false;
}
HttpSession session = request.getSession(true);
if (session.getAttribute("test168") != null
|| request.getParameter("test168") != null)
{
request.getSession(true).setAttribute("test168", Boolean.TRUE.toString());
return true;
}
return false;
}
private boolean isPortletApplication(ServletContext context,
ToolConfiguration configuration) throws ToolRenderException,
MalformedURLException
{
SakaiPortletWindow window = registry.getOrCreatePortletWindow(configuration);
if (window == null)
{
return false;
}
if (LOG.isDebugEnabled())
{
LOG.debug("Checking context for potential portlet ");
}
ServletContext crossContext = context.getContext(window.getContextPath());
if (LOG.isDebugEnabled())
{
LOG.debug("Got servlet context as " + crossContext);
LOG.debug("Getting Context for path " + window.getContextPath());
LOG.debug("Base Path " + crossContext.getRealPath("/"));
LOG.debug("Context Name " + crossContext.getServletContextName());
LOG.debug("Server Info " + crossContext.getServerInfo());
LOG.debug(" and it is a portlet ? :"
+ (crossContext.getResource("/WEB-INF/portlet.xml") != null));
}
return crossContext.getResource("/WEB-INF/portlet.xml") != null;
}
public boolean accept(Portal portal, ToolConfiguration configuration, HttpServletRequest request,
HttpServletResponse response, ServletContext context)
{
try
{
if (isIn168TestMode(request))
{
LOG.warn("In portlet test mode");
return true;
}
if (isPortletApplication(context, configuration))
{
if (LOG.isDebugEnabled())
{
LOG.debug("Tool " + configuration.getToolId() + " is a portlet");
}
return true;
}
if (LOG.isDebugEnabled())
{
LOG.debug("Tool " + configuration.getToolId() + " is not a portlet");
}
return false;
}
catch (MalformedURLException e)
{
LOG.error("Failed to render ", e);
return false;
}
catch (ToolRenderException e)
{
LOG.error("Failed to render ", e);
return false;
}
}
public void reset( ToolConfiguration configuration)
{
registry.reset(configuration);
}
public PortletDD getPortletDD(Placement placement)
{
Properties toolProperties = placement.getPlacementConfig();
String portletName = null;
String appName = null;
String fred = null;
if (toolProperties != null)
{
// System.out.println("tp = "+toolProperties);
portletName = toolProperties.getProperty(PortalService.TOOL_PORTLET_NAME);
appName = toolProperties.getProperty(PortalService.TOOL_PORTLET_APP_NAME);
fred = toolProperties.getProperty("FRED");
}
// System.out.println("appName="+appName);
// System.out.println("portletName="+portletName);
// System.out.println("fred="+fred);
Properties configProperties = placement.getConfig();
if (configProperties != null)
{
if (portletName == null)
{
portletName = configProperties
.getProperty(PortalService.TOOL_PORTLET_NAME);
}
if (appName == null)
{
appName = configProperties
.getProperty(PortalService.TOOL_PORTLET_APP_NAME);
}
}
// System.out.println("appName="+appName);
// System.out.println("portletName="+portletName);
PortletDD pdd = PortletContextManager.getManager().getPortletDescriptor(appName,
portletName);
// System.out.println("pdd="+pdd);
return pdd;
}
public boolean isPortletModeAllowed(Placement placement, String mode)
{
if (placement == null || mode == null) return false;
PortletDD pdd = getPortletDD(placement);
if (pdd == null) return true;
Iterator supports = pdd.getSupports().iterator();
while (supports.hasNext())
{
SupportsDD sup = (SupportsDD) supports.next();
Iterator modes = sup.getPortletModes().iterator();
while (modes.hasNext())
{
if (modes.next().toString().equalsIgnoreCase(mode.toString()))
{
return true;
}
}
}
return false;
}
/**
* @return the portalService
*/
public PortalService getPortalService()
{
return portalService;
}
/**
* @param portalService
* the portalService to set
*/
public void setPortalService(PortalService portalService)
{
this.portalService = portalService;
}
}