/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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
*
* http://www.apache.org/licenses/LICENSE-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.apache.jetspeed.decoration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.portlet.PortletMode;
import javax.portlet.WindowState;
import org.apache.jetspeed.JetspeedActions;
import org.apache.jetspeed.PortalReservedParameters;
import org.apache.jetspeed.cache.CacheElement;
import org.apache.jetspeed.cache.ContentCacheKey;
import org.apache.jetspeed.cache.JetspeedContentCache;
import org.apache.jetspeed.container.PortletWindow;
import org.apache.jetspeed.container.url.PortalURL;
import org.apache.jetspeed.decoration.caches.SessionPathResolverCache;
import org.apache.jetspeed.factory.PortletFactory;
import org.apache.jetspeed.om.page.ContentFragment;
import org.apache.jetspeed.om.page.ContentPage;
import org.apache.jetspeed.om.page.Fragment;
import org.apache.jetspeed.om.portlet.PortletApplication;
import org.apache.jetspeed.om.portlet.PortletDefinition;
import org.apache.jetspeed.om.portlet.Supports;
import org.apache.jetspeed.pipeline.PipelineException;
import org.apache.jetspeed.pipeline.valve.AbstractValve;
import org.apache.jetspeed.pipeline.valve.Valve;
import org.apache.jetspeed.pipeline.valve.ValveContext;
import org.apache.jetspeed.request.RequestContext;
import org.apache.jetspeed.security.SecurityAccessController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Assigns decorations and page actions to all of the portlet Fragments within
* the current request.
*
* @see org.apache.jetspeed.om.page.ContentFragment
* @see org.apache.jetspeed.om.page.ContentPage
* @see org.apache.jetspeed.decoration.Decoration
* @see org.apache.jetspeed.decoration.LayoutDecoration
* @see org.apache.jetspeed.decoration.PortletDecoration
* @see org.apache.jetspeed.decoration.Theme
*
* @author <href a="mailto:weaver@apache.org">Scott T. Weaver</a>
* @author <href a="mailto:firevelocity@gmail.com">Vivek Kumar</a>
* @version $Id$
*/
public class DecorationValve extends AbstractValve implements Valve
{
public static final String ACTION_IMAGE_EXTENSION_ATTR = "actionImageExtension";
public static final String IS_AJAX_DECORATION_REQUEST = "org.apache.jetspeed.decoration.ajax";
protected final static Logger log = LoggerFactory.getLogger(DecorationValve.class);
private final DecorationFactory decorationFactory;
private Map<String, DecoratorActionsFactory> decoratorActionsAdapterCache = Collections.synchronizedMap(new HashMap<String, DecoratorActionsFactory>());
private DecoratorActionsFactory defaultDecoratorActionsFactory;
private String defaultDecoratorActionsFactoryClassName;
private JetspeedContentCache cache = null;
private boolean useSessionForThemeCaching = false;
private boolean maxOnEdit = false;
private boolean maxOnConfig = false;
private boolean maxOnEditDefaults = false;
/**
* When edit_defaults mode is not supported by a portlet, support the mode automatically.
*/
private boolean autoSwitchingForConfigMode = false;
/**
* When edit_defaults mode is not supported by a portlet, support the mode automatically.
*/
private boolean autoSwitchingToEditDefaultsModes = true;
/**
* For security constraint checks
*/
protected SecurityAccessController accessController;
/**
* For portlet instance helper method support checks.
*/
private PortletFactory portletFactory;
public DecorationValve(DecorationFactory decorationFactory, SecurityAccessController accessController)
{
this(decorationFactory, accessController, null);
}
public DecorationValve(DecorationFactory decorationFactory,
SecurityAccessController accessController, JetspeedContentCache cache)
{
this(decorationFactory, accessController, cache, false);
}
public DecorationValve(DecorationFactory decorationFactory,
SecurityAccessController accessController, JetspeedContentCache cache,
boolean useSessionForThemeCaching)
{
this(decorationFactory, accessController, cache, useSessionForThemeCaching, null);
}
public DecorationValve(DecorationFactory decorationFactory,
SecurityAccessController accessController,
JetspeedContentCache cache, boolean useSessionForThemeCaching,
PortletFactory portletFactory)
{
this(decorationFactory, accessController, cache, useSessionForThemeCaching, null, new DefaultDecoratorActionsFactory());
}
public DecorationValve(DecorationFactory decorationFactory,
SecurityAccessController accessController,
JetspeedContentCache cache, boolean useSessionForThemeCaching,
PortletFactory portletFactory,
DecoratorActionsFactory defaultDecoratorActionsFactory)
{
this.decorationFactory = decorationFactory;
this.defaultDecoratorActionsFactory = defaultDecoratorActionsFactory;
this.defaultDecoratorActionsFactoryClassName = defaultDecoratorActionsFactory.getClass().getName();
//added the accessController in portlet decorater for checking the actions
this.accessController = accessController;
this.cache = cache;
this.useSessionForThemeCaching = useSessionForThemeCaching;
this.portletFactory = portletFactory;
}
public void invoke(RequestContext requestContext, ValveContext context) throws PipelineException
{
//long start = System.currentTimeMillis();
boolean isAjaxRequest = (context == null);
initFragments( requestContext, isAjaxRequest, null );
//long end = System.currentTimeMillis();
//System.out.println(end - start);
if (!isAjaxRequest)
{
context.invokeNext(requestContext);
}
}
public void initFragments( RequestContext requestContext, boolean isAjaxRequest, List fragments )
{
if (isAjaxRequest)
{
requestContext.setAttribute(IS_AJAX_DECORATION_REQUEST, new Boolean(true));
}
ContentPage page = requestContext.getPage();
// Globally override all psml themes if override session attribute has been set
if (requestContext
.getSessionAttribute(PortalReservedParameters.PAGE_THEME_OVERRIDE_ATTRIBUTE) != null)
{
String decoratorName = (String) requestContext
.getSessionAttribute(PortalReservedParameters.PAGE_THEME_OVERRIDE_ATTRIBUTE);
page.overrideDefaultDecorator(decoratorName, Fragment.LAYOUT);
}
PageActionAccess pageActionAccess = (PageActionAccess)requestContext.getAttribute(PortalReservedParameters.PAGE_EDIT_ACCESS_ATTRIBUTE);
String themeCacheKey = null;
ContentCacheKey themeContentCacheKey = null;
Theme theme = null;
if (useCache())
{
if (pageActionAccess.isEditing() == false)
{
// user helps us with the funky way jetspeed doesn't create a new session on login
if (this.useSessionForThemeCaching)
{
themeCacheKey = cache.createSessionKey(requestContext);
theme = (Theme) requestContext.getSessionAttribute(themeCacheKey);
}
else
{
themeContentCacheKey = cache.createCacheKey(requestContext, page.getId());
CacheElement themeCacheElem = cache.get(themeContentCacheKey);
if (themeCacheElem != null)
{
theme = (Theme) themeCacheElem.getContent();
}
}
}
}
if (theme != null)
{
theme.init(page, decorationFactory, requestContext);
requestContext.setAttribute(PortalReservedParameters.PAGE_THEME_ATTRIBUTE, theme);
boolean solo = isSoloMode(requestContext);
SessionPathResolverCache sessionPathResolver = new SessionPathResolverCache( requestContext.getRequest().getSession() );
initDepthFragmentDecorations(requestContext, theme, page.getRootFragment(),
pageActionAccess, isAjaxRequest,
((DecorationFactoryImpl) decorationFactory).getResourceValidator(),
sessionPathResolver, (theme.isInvalidated() && !solo));
if (theme.isInvalidated() && !solo)
{
if (this.useSessionForThemeCaching)
{
requestContext.setSessionAttribute(themeCacheKey, theme);
}
else
{
CacheElement themeCacheElem = cache.createElement(themeContentCacheKey, theme);
cache.put(themeCacheElem);
}
theme.setInvalidated(false);
}
return;
}
theme = decorationFactory.getTheme(page, requestContext);
requestContext.setAttribute(PortalReservedParameters.PAGE_THEME_ATTRIBUTE, theme);
if ( fragments == null || fragments.size() == 0 )
{
ContentFragment rootFragment = page.getRootFragment();
initDepthFragments(requestContext, theme, rootFragment, pageActionAccess, isAjaxRequest, fragments);
}
else
{
Iterator fragmentsIter = fragments.iterator();
while ( fragmentsIter.hasNext() )
{
ContentFragment fragment = (ContentFragment)fragmentsIter.next();
initFragment(requestContext, theme, fragment, pageActionAccess, isAjaxRequest);
}
}
if (useCache() && !isSoloMode(requestContext))
{
if (themeContentCacheKey == null && themeCacheKey == null)
{
if (this.useSessionForThemeCaching)
{
themeCacheKey = cache.createSessionKey(requestContext);
requestContext.getRequest().getSession().removeAttribute(themeCacheKey);
}
else
{
themeContentCacheKey = cache.createCacheKey(requestContext, page.getId());
cache.remove(themeContentCacheKey);
}
}
else
{
if (this.useSessionForThemeCaching)
{
themeContentCacheKey = cache.createCacheKey(requestContext, page.getId());
requestContext.setSessionAttribute(themeCacheKey, theme);
}
else
{
CacheElement themeCacheElem = cache.createElement(themeContentCacheKey, theme);
cache.put(themeCacheElem);
}
}
}
}
protected boolean isSoloMode(RequestContext requestContext)
{
boolean solo = false;
PortletWindow window = requestContext.getPortalURL().getNavigationalState().getMaximizedWindow();
boolean maximized = (window != null);
if (maximized)
{
solo = JetspeedActions.SOLO_STATE.equals(requestContext.getPortalURL().getNavigationalState().getMappedState(window));
}
return solo;
}
protected boolean useCache()
{
return this.cache != null;
}
public String toString()
{
return "DecorationValve";
}
public DecoratorActionsFactory getDecoratorActionsAdapter(Decoration decoration)
{
// read custom decorator actions factory class name from the decoration properties.
String decoratorActionsAdapterClassName = decoration.getProperty("actions.factory");
if (decoratorActionsAdapterClassName == null || "".equals(decoratorActionsAdapterClassName) || decoratorActionsAdapterClassName.equals(defaultDecoratorActionsFactoryClassName))
{
return defaultDecoratorActionsFactory;
}
DecoratorActionsFactory adapter = (DecoratorActionsFactory) decoratorActionsAdapterCache.get(decoratorActionsAdapterClassName);
if (adapter == null)
{
try
{
adapter = (DecoratorActionsFactory) Class.forName(decoratorActionsAdapterClassName).newInstance();
adapter.setMaximizeOnEdit(this.maxOnEdit);
adapter.setMaximizeOnConfig(this.maxOnConfig);
adapter.setMaximizeOnEditDefaults(this.maxOnEditDefaults);
decoratorActionsAdapterCache.put(decoratorActionsAdapterClassName, adapter);
}
catch (Exception e)
{
adapter = defaultDecoratorActionsFactory;
log.error("Failed to instantiate custom DecoratorActionsAdaptor " + decoratorActionsAdapterClassName + ", falling back to default.", e);
}
}
return adapter;
}
/**
* Builds and assigns a list of available portlet modes and window states for
* the target <code>Fragment</code>.
*
* @param requestContext RequestContext of the current portal request.
* @param fragment Fragment to initialize modes and states for.
* @return
*/
protected boolean initActionsForFragment(RequestContext requestContext,
ContentFragment fragment,
PageActionAccess pageActionAccess,
Decoration decoration,
boolean isAjaxRequest)
{
boolean fragmentSupportsActions = false;
PortletWindow window = requestContext.getPortletWindow(fragment);
if (!window.isValid())
{
return fragmentSupportsActions; // allow nothing
}
PortletDefinition portlet = window.getPortletDefinition();
List actions = Collections.EMPTY_LIST;
PortletMode currentMode = requestContext.getPortalURL().getNavigationalState().getMode(window);
WindowState currentState = requestContext.getPortalURL().getNavigationalState().getState(window);
List<Supports> supports = portlet.getSupports();
if ( fragment.equals(requestContext.getPage().getRootFragment()) )
{
fragmentSupportsActions = true;
actions = getPageModes(requestContext, window, supports, currentMode, currentState, pageActionAccess, decoration, isAjaxRequest);
}
else if ( !Fragment.LAYOUT.equals(fragment.getType()) )
{
fragmentSupportsActions = true;
String fragmentId = fragment.getId();
PortletApplication pa = window.getPortletDefinition().getApplication();
String portletName = portlet.getUniqueName();
PortletMode currentMappedMode = pa.getMappedPortletMode(currentMode);
WindowState currentMappedState = pa.getMappedWindowState(currentState);
Object action;
PortletMode mappedMode;
PortletMode customMode;
WindowState mappedState;
WindowState customState;
ArrayList actionTemplates = new ArrayList();
DecoratorActionsFactory actionsAdapter = getDecoratorActionsAdapter(decoration);
List supportedActions = actionsAdapter.getSupportedActions(requestContext, pa, window, currentMappedMode, currentMappedState, decoration);
Iterator iter = supportedActions.iterator();
String currentModeAction = null;
String currentStateAction = null;
while ( iter.hasNext() )
{
action = iter.next();
if ( action instanceof PortletMode )
{
mappedMode = (PortletMode)action;
customMode = pa.getCustomPortletMode(mappedMode);
if ( customMode != null )
{
boolean equalsCurrentMode = customMode.equals(currentMode);
if ( equalsCurrentMode )
{
currentModeAction = mappedMode.toString();
}
if ( ! equalsCurrentMode || isAjaxRequest )
{
if ( (supportsPortletMode(supports,customMode) || isAutoSwitchableCustomMode(window, customMode))
&& (!PortletMode.EDIT.equals(customMode) || pageActionAccess.isEditAllowed())
&& pageActionAccess.checkPortletMode(fragmentId, portletName, mappedMode)
)
{
actionTemplates.add(new DecoratorActionTemplate(mappedMode, customMode));
}
}
}
}
else if ( action instanceof WindowState )
{
mappedState = (WindowState)action;
customState = pa.getCustomWindowState(mappedState);
if ( customState != null )
{
boolean equalsCurrentState = customState.equals(currentState);
if ( equalsCurrentState )
{
currentStateAction = mappedState.toString();
}
if ( ! equalsCurrentState || isAjaxRequest )
{
if ( pageActionAccess.checkWindowState(fragmentId, portletName, mappedState ) )
{
actionTemplates.add(new DecoratorActionTemplate(mappedState, customState));
}
}
}
}
}
actions = actionsAdapter.getDecoratorActions(requestContext, pa, window, currentMode, currentState, decoration, actionTemplates,portlet,fragment,accessController);
decoration.setCurrentModeAction( currentModeAction );
decoration.setCurrentStateAction( currentStateAction );
}
decoration.setActions( actions );
return fragmentSupportsActions;
}
protected boolean supportsPortletMode(List<Supports> supports, PortletMode mode)
{
if(mode.equals(PortletMode.VIEW))
{
return true;
}
String pm = mode.toString();
for (Supports s : supports)
{
if (s.getPortletModes().contains(pm))
{
return true;
}
}
return false;
}
/**
* Builds a list of portlet modes that can be executed on the current
* <code>fragment</code> excluding the portlet's current mode.
*
* @param requestContext RequestContext of the current portal request.
* @param pageActionAccess
* @param mode
* @param content
* @param portletName
* @param window
* @param fragment
* @return <code>java.util.List</code> of modes excluding the current one.
*/
protected List getPageModes(RequestContext requestContext, PortletWindow window, List<Supports> supports,
PortletMode mode, WindowState state, PageActionAccess pageActionAccess, Decoration decoration,
boolean isAjaxRequest)
{
List pageModes = new ArrayList();
try
{
if (mode.equals(PortletMode.HELP) || !state.equals(WindowState.NORMAL))
{
// switch back to VIEW mode and NORMAL state.
PortalURL portalURL = requestContext.getPortalURL();
String action = requestContext.getResponse().encodeURL( (isAjaxRequest)
? portalURL.createNavigationalEncoding(window, PortletMode.VIEW, WindowState.NORMAL)
: portalURL.createPortletURL(window, PortletMode.VIEW, WindowState.NORMAL, portalURL.isSecure()).toString() );
String actionName = PortletMode.VIEW.toString();
pageModes.add(new DecoratorAction(actionName, requestContext.getLocale(), decoration.getResource("images/" + actionName + ".gif"),action,DecoratorActionTemplate.ACTION_TYPE_MODE));
}
else if ( pageActionAccess.isEditAllowed() )
{
String targetMode = pageActionAccess.isEditing() ? PortletMode.VIEW.toString() : PortletMode.EDIT.toString();
PortalURL portalURL = requestContext.getPortalURL();
HashMap parameters = new HashMap();
String[] paramValues = new String[]{targetMode};
parameters.put("pageMode",paramValues);
// Use an ActionURL to set the oposite pageMode and always set VIEW mode and state NORMAL
String action = requestContext.getResponse().encodeURL( (isAjaxRequest)
? portalURL.createNavigationalEncoding(window, parameters, PortletMode.VIEW, WindowState.NORMAL, true)
: portalURL.createPortletURL(window, parameters, PortletMode.VIEW, WindowState.NORMAL, true, portalURL.isSecure()).toString() );
pageModes.add(new DecoratorAction(targetMode, requestContext.getLocale(), decoration.getResource("images/" + targetMode + ".gif"), action,DecoratorActionTemplate.ACTION_TYPE_MODE));
if (supportsPortletMode(supports,PortletMode.HELP))
{
if ( pageActionAccess.isEditing() )
{
// force it back to VIEW mode first with an ActionURL, as well as setting HELP mode and MAXIMIZED state
paramValues[0] = PortletMode.VIEW.toString();
action = requestContext.getResponse().encodeURL( (isAjaxRequest)
? portalURL.createNavigationalEncoding(window, parameters, PortletMode.HELP, WindowState.MAXIMIZED, true)
: portalURL.createPortletURL(window, parameters, PortletMode.HELP, WindowState.MAXIMIZED, true, portalURL.isSecure()).toString() );
}
else
{
// switch to mode HELP and state MAXIMIZED
action = requestContext.getResponse().encodeURL( (isAjaxRequest)
? portalURL.createNavigationalEncoding(window, PortletMode.HELP, WindowState.MAXIMIZED)
: portalURL.createPortletURL(window,PortletMode.HELP, WindowState.MAXIMIZED, portalURL.isSecure()).toString() );
}
String actionName = PortletMode.HELP.toString();
pageModes.add(new DecoratorAction(actionName, requestContext.getLocale(), decoration.getResource("images/" + actionName + ".gif"), action,DecoratorActionTemplate.ACTION_TYPE_MODE));
}
}
}
catch (Exception e)
{
log.warn("Unable to initalize PageLayout actions", e);
pageModes = null;
}
return pageModes;
}
/**
* Intializes all fragments with there decorations and portlet modes
* and winodw states.
*
*
* @param requestContext RequestContext of the current portal request.
* @param theme
* @param fragment
* @param pageActionAccess
*/
protected void initDepthFragments(RequestContext requestContext,
Theme theme,
ContentFragment fragment,
PageActionAccess pageActionAccess,
boolean isAjaxRequest,
List collectFragments )
{
final List contentFragments = fragment.getFragments();
if(contentFragments != null && contentFragments.size() > 0)
{
Iterator itr = contentFragments.iterator();
while(itr.hasNext())
{
ContentFragment aFragment = (ContentFragment) itr.next();
initDepthFragments(requestContext, theme, aFragment, pageActionAccess, isAjaxRequest, collectFragments);
}
}
if ( initFragment(requestContext, theme, fragment, pageActionAccess, isAjaxRequest) )
{
if ( collectFragments != null )
{
collectFragments.add( fragment );
}
}
}
protected boolean initFragment(RequestContext requestContext,
Theme theme,
ContentFragment fragment,
PageActionAccess pageActionAccess,
boolean isAjaxRequest)
{
boolean fragmentSupportsActions = false;
try
{
Decoration decoration = theme.getDecoration(fragment);
fragment.setDecoration(decoration);
fragmentSupportsActions = initActionsForFragment(requestContext, fragment, pageActionAccess, decoration, isAjaxRequest);
}
catch (Exception e)
{
log.warn("Unable to initalize actions for fragment "+fragment.getId(), e);
}
return fragmentSupportsActions;
}
/**
* Reintializes all fragments with there decorations and portlet modes
* and winodw states after theme is restored from cache.
*
* @param requestContext RequestContext of the current portal request.
* @param theme
* @param fragment
* @param pageActionAccess
* @param isAjaxRequest
* @param validator
* @param pathResolverCache
*/
protected void initDepthFragmentDecorations(RequestContext requestContext,
Theme theme,
ContentFragment fragment,
PageActionAccess pageActionAccess,
boolean isAjaxRequest,
ResourceValidator validator,
PathResolverCache pathResolverCache,
boolean reloadActionList)
{
final List contentFragments = fragment.getFragments();
if(contentFragments != null && contentFragments.size() > 0)
{
Iterator itr = contentFragments.iterator();
while(itr.hasNext())
{
ContentFragment aFragment = (ContentFragment) itr.next();
initDepthFragmentDecorations(requestContext, theme, aFragment,
pageActionAccess, isAjaxRequest,
validator, pathResolverCache, reloadActionList);
}
}
try
{
// PageTheme::getDecoration retrieves cached decoration only.
Decoration decoration = theme.getDecoration(fragment);
// re-init to set transient memebers.
Properties config = ((DecorationFactoryImpl) decorationFactory).getConfiguration(decoration.getName(), fragment.getType());
((BaseDecoration) decoration).init(config, validator, pathResolverCache);
// fragment is newly created on every request, so reset decoration for fragment.
fragment.setDecoration(decoration);
if (reloadActionList)
{
initActionsForFragment(requestContext, fragment, pageActionAccess, decoration, isAjaxRequest);
}
}
catch (Exception e)
{
log.warn("Unable to initalize actions for fragment "+fragment.getId(), e);
}
}
public void setMaximizeOnEdit(boolean maxOnEdit)
{
this.maxOnEdit = maxOnEdit;
this.defaultDecoratorActionsFactory.setMaximizeOnEdit(maxOnEdit);
}
public boolean getMaximizeOnEdit()
{
return this.maxOnEdit;
}
public void setMaximizeOnConfig(boolean maxOnConfig)
{
this.maxOnConfig = maxOnConfig;
this.defaultDecoratorActionsFactory.setMaximizeOnConfig(maxOnConfig);
}
public boolean getMaximizeOnConfig()
{
return this.maxOnConfig;
}
public void setMaximizeOnEditDefaults(boolean maxOnEditDefaults)
{
this.maxOnEditDefaults = maxOnEditDefaults;
this.defaultDecoratorActionsFactory.setMaximizeOnEditDefaults(maxOnEditDefaults);
}
public boolean getMaximizeOnEditDefaults()
{
return this.maxOnEditDefaults;
}
public void setAutoSwitchingToEditDefaultsModes(boolean autoSwitchingToEditDefaultsModes)
{
this.autoSwitchingToEditDefaultsModes = autoSwitchingToEditDefaultsModes;
}
public boolean getAutoSwitchingToEditDefaultsModes()
{
return this.autoSwitchingToEditDefaultsModes;
}
public void setAutoSwitchingForConfigMode(boolean autoSwitchingForConfigMode)
{
this.autoSwitchingForConfigMode = autoSwitchingForConfigMode;
}
public boolean getAutoSwitchingForConfigMode()
{
return this.autoSwitchingForConfigMode;
}
private boolean isAutoSwitchableCustomMode(PortletWindow window, PortletMode customMode)
{
if (this.autoSwitchingForConfigMode && JetspeedActions.CONFIG_MODE.equals(customMode))
{
return true;
}
if (this.autoSwitchingToEditDefaultsModes && JetspeedActions.EDIT_DEFAULTS_MODE.equals(customMode) && portletFactory != null)
{
if (portletFactory.hasRenderHelperMethod(window.getPortletDefinition(), PortletMode.EDIT))
{
return true;
}
}
return false;
}
}