/**
* 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.layout;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.xml.stream.XMLEventReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.PortalException;
import org.apereo.portal.layout.node.IUserLayoutChannelDescription;
import org.apereo.portal.layout.node.IUserLayoutNodeDescription;
import org.apereo.portal.layout.node.IUserLayoutNodeDescription.LayoutNodeType;
import org.apereo.portal.layout.node.UserLayoutChannelDescription;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletDefinitionParameter;
import org.apereo.portal.spring.locator.PortletDefinitionRegistryLocator;
import org.w3c.dom.Document;
/**
* Wraps {@link IUserLayoutManager} interface to provide ability to incorporate channels into a user
* layout that are not part of their layout structure (persistent).
*
* <p>The channels are incorporated upon request (functional name) and remain part of the layout
* structure only as long as they are the target channel.
*
*/
public class TransientUserLayoutManagerWrapper implements IUserLayoutManager {
private static final Log log = LogFactory.getLog(TransientUserLayoutManagerWrapper.class);
// transient folder's subscribe id <'f'older><'t'ransient><id>
public static final String TRANSIENT_FOLDER_ID = "ft1";
// channel subscription prefix <'c'hannel><'t'ransient><'f'older>
public static final String SUBSCRIBE_PREFIX = "ctf";
// The original user layout manager
private IUserLayoutManager man = null;
// contains fname --> subscribe id mappings (for transient channels only)
private Map<String, String> mFnameMap =
Collections.synchronizedMap(new HashMap<String, String>());
// contains subscribe id --> fname mappings (for transient channels only)
private Map<String, String> mSubIdMap =
Collections.synchronizedMap(new HashMap<String, String>());
// stores channel defs by subscribe id (transient channels only)
private Map<String, IPortletDefinition> mChanMap =
Collections.synchronizedMap(new HashMap<String, IPortletDefinition>());
// current root/focused subscribe id
private String mFocusedId = "";
// subscription id counter for generating subscribe ids
private int mSubId = 0;
public TransientUserLayoutManagerWrapper(IUserLayoutManager manager) throws PortalException {
this.man = manager;
if (man == null) {
throw new PortalException("Cannot wrap a null IUserLayoutManager !");
}
}
public IUserLayout getUserLayout() throws PortalException {
return man.getUserLayout();
}
@Override
public XMLEventReader getUserLayoutReader() {
final XMLEventReader userLayoutReader = man.getUserLayoutReader();
return new TransientUserLayoutXMLEventReader(this, userLayoutReader);
}
public Document getUserLayoutDOM() throws PortalException {
return man.getUserLayoutDOM();
}
public void loadUserLayout() throws PortalException {
man.loadUserLayout();
}
public void loadUserLayout(boolean reload) throws PortalException {
man.loadUserLayout(reload);
}
public void saveUserLayout() throws PortalException {
man.saveUserLayout();
}
@Override
public Set<String> getAllSubscribedChannels() {
final Set<String> allSubscribedChannels =
new LinkedHashSet<String>(man.getAllSubscribedChannels());
for (final String subscribeId : mSubIdMap.keySet()) {
allSubscribedChannels.add(subscribeId);
}
return allSubscribedChannels;
}
public IUserLayoutNodeDescription getNode(String nodeId) throws PortalException {
// check to see if it's in the layout first, if not then
// build it..
IUserLayoutNodeDescription ulnd = null;
// assume that not finding it in the implementation
// means that it may be a requested (transient) node.
try {
ulnd = man.getNode(nodeId);
} catch (PortalException pe) {
if (log.isDebugEnabled())
log.debug(
"Node '"
+ nodeId
+ "' is not in layout, "
+ "checking for a transient node...");
}
if (null == ulnd) {
// if the requested node hasn't been returned yet, it's
// likely it's a transient node that isn't actually part of
// the layout
ulnd = getTransientNode(nodeId);
}
return ulnd;
}
public IUserLayoutNodeDescription addNode(
IUserLayoutNodeDescription node, String parentId, String nextSiblingId)
throws PortalException {
return man.addNode(node, parentId, nextSiblingId);
}
public boolean moveNode(String nodeId, String parentId, String nextSiblingId)
throws PortalException {
// allow all moves, except for those related to the transient channels and folders
if (nodeId != null
&& (!mSubIdMap.containsKey(nodeId))
&& (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
return man.moveNode(nodeId, parentId, nextSiblingId);
} else {
return false;
}
}
public boolean deleteNode(String nodeId) throws PortalException {
// allow all deletions, except for those related to the transient channels and folders
if (nodeId != null
&& (!mSubIdMap.containsKey(nodeId))
&& (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
return man.deleteNode(nodeId);
} else {
return false;
}
}
public boolean updateNode(IUserLayoutNodeDescription node) throws PortalException {
// allow all updates, except for those related to the transient channels and folders
String nodeId = node.getId();
if (nodeId != null
&& (!mSubIdMap.containsKey(nodeId))
&& (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
return man.updateNode(node);
} else {
return false;
}
}
public boolean canAddNode(
IUserLayoutNodeDescription node, String parentId, String nextSiblingId)
throws PortalException {
return man.canAddNode(node, parentId, nextSiblingId);
}
public boolean canMoveNode(String nodeId, String parentId, String nextSiblingId)
throws PortalException {
// allow all moves, except for those related to the transient channels and folders
if (nodeId != null
&& (!mSubIdMap.containsKey(nodeId))
&& (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
return man.canMoveNode(nodeId, parentId, nextSiblingId);
} else {
return false;
}
}
public boolean canDeleteNode(String nodeId) throws PortalException {
// allow all deletions, except for those related to the transient channels and folders
if (nodeId != null
&& (!mSubIdMap.containsKey(nodeId))
&& (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
return man.canDeleteNode(nodeId);
} else {
return false;
}
}
public boolean canUpdateNode(IUserLayoutNodeDescription node) throws PortalException {
// allow all updates, except for those related to the transient channels and folders
String nodeId = node.getId();
if (nodeId != null
&& (!mSubIdMap.containsKey(nodeId))
&& (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
return man.canUpdateNode(node);
} else {
return false;
}
}
public void markAddTargets(IUserLayoutNodeDescription node) throws PortalException {
man.markAddTargets(node);
}
public void markMoveTargets(String nodeId) throws PortalException {
man.markMoveTargets(nodeId);
}
public String getParentId(String nodeId) throws PortalException {
if (mChanMap.containsKey(nodeId) || TRANSIENT_FOLDER_ID.equals(nodeId)) {
return null;
}
return man.getParentId(nodeId);
}
public Enumeration getChildIds(String nodeId) throws PortalException {
return man.getChildIds(nodeId);
}
public String getNextSiblingId(String nodeId) throws PortalException {
return man.getNextSiblingId(nodeId);
}
public String getPreviousSiblingId(String nodeId) throws PortalException {
return man.getPreviousSiblingId(nodeId);
}
public String getCacheKey() throws PortalException {
// we don't need to worry about extending the base cache key here,
// because the transient channels are always rendered in a focused
// mode, that means that the user preference attributes will be
// sufficient to describe the layout state.
// In general, however one would need to append that focused channel
// subscribe id and fname (both are required for global scope use).
return man.getCacheKey();
}
public int getLayoutId() {
return man.getLayoutId();
}
public String getRootFolderId() {
return man.getRootFolderId();
}
/**
* Returns the depth of a node in the layout tree.
*
* @param nodeId a <code>String</code> value
* @return a depth value
* @exception PortalException if an error occurs
*/
public int getDepth(String nodeId) throws PortalException {
return man.getDepth(nodeId);
}
public IUserLayoutNodeDescription createNodeDescription(LayoutNodeType nodeType)
throws PortalException {
return man.createNodeDescription(nodeType);
}
/**
* Given a subscribe Id, return a ChannelDefinition.
*
* @param subId the subscribe id for the ChannelDefinition.
* @return a <code>ChannelDefinition</code>
*/
protected IPortletDefinition getChannelDefinition(String subId) throws PortalException {
IPortletDefinition chanDef = mChanMap.get(subId);
if (null == chanDef) {
String fname = getFname(subId);
if (log.isDebugEnabled())
log.debug(
"TransientUserLayoutManagerWrapper>>getChannelDefinition, "
+ "attempting to get a channel definition using functional name: "
+ fname);
try {
chanDef =
PortletDefinitionRegistryLocator.getPortletDefinitionRegistry()
.getPortletDefinitionByFname(fname);
} catch (Exception e) {
throw new PortalException(
"Failed to get channel information " + "for subscribeId: " + subId);
}
mChanMap.put(subId, chanDef);
}
return chanDef;
}
/**
* Given a subscribe Id, return its functional name.
*
* @param subId the subscribe id to lookup
* @return the subscribe id's functional name
*/
public String getFname(String subId) {
return mSubIdMap.get(subId);
}
public boolean isTransientChannel(String subId) {
return mSubIdMap.containsKey(subId);
}
/**
* Given an functional name, return its subscribe id.
*
* @param fname the functional name to lookup
* @return the fname's subscribe id.
*/
public String getSubscribeId(String fname) throws PortalException {
// see if a given subscribe id is already in the map
String subId = mFnameMap.get(fname);
if (subId == null) {
// see if a given subscribe id is already in the layout
subId = man.getSubscribeId(fname);
}
// obtain a description of the transient channel and
// assign a new transient channel id
if (subId == null) {
try {
IPortletDefinition chanDef =
PortletDefinitionRegistryLocator.getPortletDefinitionRegistry()
.getPortletDefinitionByFname(fname);
if (chanDef != null) {
// assign a new id
subId = getNextSubscribeId();
mFnameMap.put(fname, subId);
mSubIdMap.put(subId, fname);
mChanMap.put(subId, chanDef);
}
} catch (Exception e) {
log.error(
"TransientUserLayoutManagerWrapper::getSubscribeId() : "
+ "an exception encountered while trying to obtain "
+ "ChannelDefinition for fname \""
+ fname
+ "\" : "
+ e);
subId = null;
}
}
return subId;
}
/* (non-Javadoc)
* @see org.apereo.portal.layout.IUserLayoutManager#getSubscribeId(java.lang.String, java.lang.String)
*/
@Override
public String getSubscribeId(String parentFolderId, String fname) {
return this.man.getSubscribeId(parentFolderId, fname);
}
/** Get the current focused layout subscribe id. */
public String getFocusedId() {
return mFocusedId;
}
/**
* Set the current focused layout subscribe id.
*
* @param subscribeId Id to be set as focused.
*/
public void setFocusedId(String subscribeId) {
mFocusedId = subscribeId;
}
/**
* Return an IUserLayoutChannelDescription by way of nodeId
*
* @param nodeId the node (subscribe) id to get the channel for.
* @return a <code>IUserLayoutNodeDescription</code>
*/
private IUserLayoutChannelDescription getTransientNode(String nodeId) throws PortalException {
// get fname from subscribe id
final String fname = getFname(nodeId);
if (null == fname || fname.equals("")) {
return null;
}
try {
// check cache first
IPortletDefinition chanDef = mChanMap.get(nodeId);
if (null == chanDef) {
chanDef =
PortletDefinitionRegistryLocator.getPortletDefinitionRegistry()
.getPortletDefinitionByFname(fname);
mChanMap.put(nodeId, chanDef);
}
return createUserLayoutChannelDescription(nodeId, chanDef);
} catch (Exception e) {
throw new PortalException("Failed to obtain channel definition using fname: " + fname);
}
}
protected IUserLayoutChannelDescription createUserLayoutChannelDescription(
String nodeId, IPortletDefinition chanDef) {
IUserLayoutChannelDescription ulnd = new UserLayoutChannelDescription();
ulnd.setId(nodeId);
ulnd.setName(chanDef.getName());
ulnd.setUnremovable(true);
ulnd.setImmutable(true);
ulnd.setHidden(false);
ulnd.setTitle(chanDef.getTitle());
ulnd.setDescription(chanDef.getDescription());
ulnd.setChannelPublishId("" + chanDef.getPortletDefinitionId().getStringId());
ulnd.setChannelTypeId("" + chanDef.getType().getId());
ulnd.setFunctionalName(chanDef.getFName());
ulnd.setTimeout(chanDef.getTimeout());
Set<IPortletDefinitionParameter> parms = chanDef.getParameters();
for (IPortletDefinitionParameter parm : parms) {
ulnd.setParameterValue(parm.getName(), parm.getValue());
}
return ulnd;
}
/**
* Return the next sequential subscription id.
*
* @return a subscribe id
*/
private synchronized String getNextSubscribeId() {
mSubId++;
return SUBSCRIBE_PREFIX + mSubId;
}
}