/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/portal/trunk/portal-service-impl/impl/src/java/org/sakaiproject/portal/service/PortalServiceImpl.java $
* $Id: PortalServiceImpl.java 122221 2013-04-04 21:24:12Z ottenhoff@longsight.com $
***********************************************************************************
*
* 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.service;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.core.PortletContextManager;
import org.apache.pluto.descriptors.portlet.PortletAppDD;
import org.apache.pluto.descriptors.portlet.PortletDD;
import org.apache.pluto.internal.InternalPortletContext;
import org.apache.pluto.spi.optional.PortletRegistryService;
import org.exolab.castor.util.LocalConfiguration;
import org.exolab.castor.util.Configuration.Property;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.portal.api.BaseEditor;
import org.sakaiproject.portal.api.Editor;
import org.sakaiproject.portal.api.EditorRegistry;
import org.sakaiproject.portal.api.Portal;
import org.sakaiproject.portal.api.PortalHandler;
import org.sakaiproject.portal.api.PortalRenderEngine;
import org.sakaiproject.portal.api.PortalService;
import org.sakaiproject.portal.api.PortletApplicationDescriptor;
import org.sakaiproject.portal.api.PortletDescriptor;
import org.sakaiproject.portal.api.SiteNeighbourhoodService;
import org.sakaiproject.portal.api.StoredState;
import org.sakaiproject.portal.api.StyleAbleProvider;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.cover.SiteService;
import org.sakaiproject.tool.api.Placement;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.cover.SessionManager;
/**
* @author ieb
* @since Sakai 2.4
* @version $Rev: 122221 $
*/
public class PortalServiceImpl implements PortalService
{
private static final Log log = LogFactory.getLog(PortalServiceImpl.class);
/**
* Parameter to force state reset
*/
public static final String PARM_STATE_RESET = "sakai.state.reset";
private static final String PORTAL_SKIN_NEOPREFIX_PROPERTY = "portal.neoprefix";
private static final String PORTAL_SKIN_NEOPREFIX_DEFAULT = "neo-";
private static String portalSkinPrefix;
private Map<String, PortalRenderEngine> renderEngines = new ConcurrentHashMap<String, PortalRenderEngine>();
private Map<String, Map<String, PortalHandler>> handlerMaps = new ConcurrentHashMap<String, Map<String, PortalHandler>>();
private Map<String, Portal> portals = new ConcurrentHashMap<String, Portal>();
private ServerConfigurationService serverConfigurationService;
private StyleAbleProvider stylableServiceProvider;
private SiteNeighbourhoodService siteNeighbourhoodService;
private String m_portalLinks;
private ContentHostingService contentHostingService;
private EditorRegistry editorRegistry;
private Editor noopEditor = new BaseEditor("noop", "noop", "", "");
public void init()
{
try
{
stylableServiceProvider = (StyleAbleProvider) ComponentManager
.get(StyleAbleProvider.class.getName());
serverConfigurationService = (ServerConfigurationService) ComponentManager
.get(ServerConfigurationService.class.getName());
try
{
// configure the parser for castor.. before anything else get a
// chance
Properties castorProperties = LocalConfiguration.getDefault();
String parser = serverConfigurationService.getString(
"sakai.xml.sax.parser",
"com.sun.org.apache.xerces.internal.parsers.SAXParser");
log.info("Configured Castor to use SAX Parser " + parser);
castorProperties.put(Property.Parser, parser);
}
catch (Exception ex)
{
log.error("Failed to configure Castor", ex);
}
portalSkinPrefix = serverConfigurationService.getString(PORTAL_SKIN_NEOPREFIX_PROPERTY, PORTAL_SKIN_NEOPREFIX_DEFAULT);
if (portalSkinPrefix == null) {
portalSkinPrefix = "";
}
}
catch (Exception ex)
{
}
if (stylableServiceProvider == null)
{
log.info("No Styleable Provider found, the portal will not be stylable");
}
}
public StoredState getStoredState()
{
Session s = SessionManager.getCurrentSession();
StoredState ss = (StoredState) s.getAttribute("direct-stored-state");
log.debug("Got Stored State as [" + ss + "]");
return ss;
}
public void setStoredState(StoredState ss)
{
Session s = SessionManager.getCurrentSession();
if (s.getAttribute("direct-stored-state") == null || ss == null)
{
StoredState ssx = (StoredState) s.getAttribute("direct-stored-state");
log.debug("Removing Stored state " + ssx);
if (ssx != null)
{
Exception ex = new Exception("traceback");
log.debug("Removing active Stored State Traceback gives location ", ex);
}
s.setAttribute("direct-stored-state", ss);
log.debug(" Set StoredState as [" + ss + "]");
}
}
private static final String TOOLSTATE_PARAM_PREFIX = "toolstate-";
private static String computeToolStateParameterName(String placementId)
{
return TOOLSTATE_PARAM_PREFIX + placementId;
}
public String decodeToolState(Map<String, String[]> params, String placementId)
{
String attrname = computeToolStateParameterName(placementId);
String[] attrval = params.get(attrname);
return attrval == null ? null : attrval[0];
}
public Map<String, String[]> encodeToolState(String placementId, String URLstub)
{
String attrname = computeToolStateParameterName(placementId);
Map<String, String[]> togo = new HashMap<String, String[]>();
// could assemble state from other visible tools here
togo.put(attrname, new String[] { URLstub });
return togo;
}
// To allow us to retain reset state across redirects
public String getResetState()
{
Session s = SessionManager.getCurrentSession();
String ss = (String) s.getAttribute("reset-stored-state");
return ss;
}
public void setResetState(String ss)
{
Session s = SessionManager.getCurrentSession();
if (s.getAttribute("reset-stored-state") == null || ss == null)
{
s.setAttribute("reset-stored-state", ss);
}
}
public boolean isEnableDirect()
{
boolean directEnable = "true".equals(serverConfigurationService.getString(
"charon.directurl", "true"));
log.debug("Direct Enable is " + directEnable);
return directEnable;
}
public boolean isResetRequested(HttpServletRequest req)
{
return "true".equals(req.getParameter(PARM_STATE_RESET))
|| "true".equals(getResetState());
}
public String getResetStateParam()
{
// TODO Auto-generated method stub
return PARM_STATE_RESET;
}
public StoredState newStoredState(String marker, String replacement)
{
log.debug("Storing State for Marker=[" + marker + "] replacement=[" + replacement
+ "]");
return new StoredStateImpl(marker, replacement);
}
public Iterator<PortletApplicationDescriptor> getRegisteredApplications()
{
PortletRegistryService registry = PortletContextManager.getManager();
final Iterator apps = registry.getRegisteredPortletApplications();
return new Iterator<PortletApplicationDescriptor>()
{
public boolean hasNext()
{
return apps.hasNext();
}
public PortletApplicationDescriptor next()
{
final InternalPortletContext pc = (InternalPortletContext) apps.next();
final PortletAppDD appDD = pc.getPortletApplicationDefinition();
return new PortletApplicationDescriptor()
{
public String getApplicationContext()
{
return pc.getPortletContextName();
}
public String getApplicationId()
{
return pc.getApplicationId();
}
public String getApplicationName()
{
return pc.getApplicationId();
}
public Iterator<PortletDescriptor> getPortlets()
{
if (appDD != null)
{
List portlets = appDD.getPortlets();
final Iterator portletsI = portlets.iterator();
return new Iterator<PortletDescriptor>()
{
public boolean hasNext()
{
return portletsI.hasNext();
}
public PortletDescriptor next()
{
final PortletDD pdd = (PortletDD) portletsI.next();
return new PortletDescriptor()
{
public String getPortletId()
{
return pdd.getPortletName();
}
public String getPortletName()
{
return pdd.getPortletName();
}
};
}
public void remove()
{
}
};
}
else
{
log.warn(" Portlet Application has no portlets "
+ pc.getPortletContextName());
return new Iterator<PortletDescriptor>()
{
public boolean hasNext()
{
return false;
}
public PortletDescriptor next()
{
return null;
}
public void remove()
{
}
};
}
}
};
}
public void remove()
{
}
};
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#getRenderEngine(javax.servlet.http.HttpServletRequest)
*/
public PortalRenderEngine getRenderEngine(String context, HttpServletRequest request)
{
// at this point we ignore request but we might use ut to return more
// than one render engine
if (context == null || context.length() == 0)
{
context = Portal.DEFAULT_PORTAL_CONTEXT;
}
return (PortalRenderEngine) renderEngines.get(context);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#addRenderEngine(org.sakaiproject.portal.api.PortalRenderEngine)
*/
public void addRenderEngine(String context, PortalRenderEngine vengine)
{
renderEngines.put(context, vengine);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#removeRenderEngine(org.sakaiproject.portal.api.PortalRenderEngine)
*/
public void removeRenderEngine(String context, PortalRenderEngine vengine)
{
renderEngines.remove(context);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#addHandler(java.lang.String,
* org.sakaiproject.portal.api.PortalHandler)
*/
public void addHandler(Portal portal, PortalHandler handler)
{
String portalContext = portal.getPortalContext();
Map<String, PortalHandler> handlerMap = getHandlerMap(portal);
String urlFragment = handler.getUrlFragment();
PortalHandler ph = handlerMap.get(urlFragment);
if (ph != null)
{
handler.deregister(portal);
log.warn("Handler Present on " + urlFragment + " will replace " + ph
+ " with " + handler);
}
handler.register(portal, this, portal.getServletContext());
handlerMap.put(urlFragment, handler);
log.info("URL " + portalContext + ":/" + urlFragment + " will be handled by "
+ handler);
}
public void addHandler(String portalContext, PortalHandler handler)
{
Portal portal = portals.get(portalContext);
if (portal == null)
{
Map<String, PortalHandler> handlerMap = getHandlerMap(portalContext, true);
handlerMap.put(handler.getUrlFragment(), handler);
log.debug("Registered handler ("+ handler+ ") for portal ("+portalContext+ ") that doesn't yet exist.");
}
else
{
addHandler(portal, handler);
}
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#getHandlerMap(java.lang.String)
*/
public Map<String, PortalHandler> getHandlerMap(Portal portal)
{
return getHandlerMap(portal.getPortalContext(), true);
}
private Map<String, PortalHandler> getHandlerMap(String portalContext, boolean create)
{
Map<String, PortalHandler> handlerMap = handlerMaps.get(portalContext);
if (create && handlerMap == null)
{
handlerMap = new ConcurrentHashMap<String, PortalHandler>();
handlerMaps.put(portalContext, handlerMap);
}
return handlerMap;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#removeHandler(java.lang.String,
* java.lang.String) This method it NOT thread safe, but the likelyhood
* of a co
*/
public void removeHandler(Portal portal, String urlFragment)
{
Map<String, PortalHandler> handlerMap = getHandlerMap(portal.getPortalContext(), false);
if (handlerMap != null)
{
PortalHandler ph = handlerMap.get(urlFragment);
if (ph != null)
{
ph.deregister(portal);
handlerMap.remove(urlFragment);
log.warn("Handler Present on " + urlFragment + " " + ph
+ " will be removed ");
}
}
}
public void removeHandler(String portalContext, String urlFragment)
{
Portal portal = portals.get(portalContext);
if (portal == null)
{
log.warn("Attempted to remove handler("+ urlFragment+ ") from non existent portal ("+portalContext+")");
}
else
{
removeHandler(portal, urlFragment);
}
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#addPortal(org.sakaiproject.portal.api.Portal)
*/
public void addPortal(Portal portal)
{
String portalContext = portal.getPortalContext();
portals.put(portalContext, portal);
// reconnect any handlers
Map<String, PortalHandler> phm = getHandlerMap(portal);
for (Iterator<PortalHandler> pIterator = phm.values().iterator(); pIterator
.hasNext();)
{
PortalHandler ph = pIterator.next();
ph.register(portal, this, portal.getServletContext());
}
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#removePortal(org.sakaiproject.portal.api.Portal)
*/
public void removePortal(Portal portal)
{
String portalContext = portal.getPortalContext();
portals.remove(portalContext);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.portal.api.PortalService#getStylableService()
*/
public StyleAbleProvider getStylableService()
{
return stylableServiceProvider;
}
/* (non-Javadoc)
* @see org.sakaiproject.portal.api.PortalService#getSiteNeighbourhoodService()
*/
public SiteNeighbourhoodService getSiteNeighbourhoodService()
{
return siteNeighbourhoodService;
}
/**
* @param siteNeighbourhoodService the siteNeighbourhoodService to set
*/
public void setSiteNeighbourhoodService(SiteNeighbourhoodService siteNeighbourhoodService)
{
this.siteNeighbourhoodService = siteNeighbourhoodService;
}
/* optional portal links for portal header (SAK-22912)
*/
public String getPortalLinks()
{
return m_portalLinks;
}
public ContentHostingService getContentHostingService() {
return contentHostingService;
}
/**
* @param portalLinks the portal icons to set
*/
public void setPortalLinks(String portalLinks)
{
m_portalLinks = portalLinks;
}
public void setContentHostingService(ContentHostingService contentHostingService) {
this.contentHostingService = contentHostingService;
}
public String getBrowserCollectionId(Placement placement) {
String collectionId = null;
if (placement != null) {
collectionId = getContentHostingService().getSiteCollection(placement.getContext());
}
if (collectionId == null) {
collectionId = getContentHostingService().getSiteCollection("~" + SessionManager.getCurrentSessionUserId());
}
return collectionId;
}
public Editor getActiveEditor() {
return getActiveEditor(null);
}
public Editor getActiveEditor(Placement placement) {
String systemEditor = serverConfigurationService.getString("wysiwyg.editor", "ckeditor");
String activeEditor = systemEditor;
if (placement != null) {
//Allow tool- or user-specific editors?
try {
Site site = SiteService.getSite(placement.getContext());
Object o = site.getProperties().get("wysiwyg.editor");
if (o != null) {
activeEditor = o.toString();
}
}
catch (IdUnusedException ex) {
if (log.isDebugEnabled()) {
log.debug(ex.getMessage());
}
}
}
Editor editor = getEditorRegistry().getEditor(activeEditor);
if (editor == null) {
// Load a base no-op editor so sakai.editor.launch calls succeed.
// We may decide to offer some textarea infrastructure as well. In
// this case, there are editor and launch files being consulted
// already from /library/, which is easier to patch and deploy.
editor = getEditorRegistry().getEditor("textarea");
}
if (editor == null) {
// If, for some reason, our stub editor is null, give an instance
// that doesn't even try to load files. This will result in script
// errors because sakai.editor.launch will not be defined, but
// this way, we can't suffer NPEs. In some cases, this degradation
// will be graceful enough that the page can function.
editor = noopEditor;
}
return editor;
}
public EditorRegistry getEditorRegistry() {
return editorRegistry;
}
public void setEditorRegistry(EditorRegistry editorRegistry) {
this.editorRegistry = editorRegistry;
}
public String getSkinPrefix() {
return portalSkinPrefix;
}
}