/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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.jasig.portal.layout.dlm.remoting;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.beanutils.BeanPredicate;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.EqualPredicate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.jasig.portal.IUserIdentityStore;
import org.jasig.portal.PortalException;
import org.jasig.portal.UserPreferencesManager;
import org.jasig.portal.fragment.subscribe.IUserFragmentSubscription;
import org.jasig.portal.fragment.subscribe.dao.IUserFragmentSubscriptionDao;
import org.jasig.portal.groups.IEntity;
import org.jasig.portal.layout.IStylesheetUserPreferencesService;
import org.jasig.portal.layout.IStylesheetUserPreferencesService.PreferencesScope;
import org.jasig.portal.layout.IUserLayoutManager;
import org.jasig.portal.layout.IUserLayoutStore;
import org.jasig.portal.layout.dlm.Constants;
import org.jasig.portal.layout.dlm.DistributedUserLayout;
import org.jasig.portal.layout.dlm.UserPrefsHandler;
import org.jasig.portal.layout.node.IUserLayoutChannelDescription;
import org.jasig.portal.layout.node.IUserLayoutFolderDescription;
import org.jasig.portal.layout.node.IUserLayoutNodeDescription;
import org.jasig.portal.layout.node.UserLayoutChannelDescription;
import org.jasig.portal.layout.node.UserLayoutFolderDescription;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletWindow;
import org.jasig.portal.portlet.registry.IPortletDefinitionRegistry;
import org.jasig.portal.portlet.registry.IPortletWindowRegistry;
import org.jasig.portal.portlets.favorites.FavoritesUtils;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.PersonFactory;
import org.jasig.portal.security.provider.RestrictedPerson;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.services.GroupService;
import org.jasig.portal.user.IUserInstance;
import org.jasig.portal.user.IUserInstanceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* Provides targets for AJAX preference setting calls.
*
* @author jennifer.bourey@yale.edu
* @version $Revision$ $Date$
*/
@Controller
@RequestMapping("/layout")
public class UpdatePreferencesServlet {
private static final String TAB_GROUP_PARAMETER = "tabGroup"; // matches incoming JS
private static final String TAB_GROUP_DEFAULT = "DEFAULT_TABGROUP"; // matches default in structure transform
private static final String ADDTAB_PERMISSION_OWNER = "UP_SYSTEM";
private static final String ADDTAB_PERMISSION_ACTIVITY = "ADD_TAB";
private static final String ADDTAB_PERMISSION_TARGET = "ALL";
protected final Logger log = LoggerFactory.getLogger(getClass());
private IPortletDefinitionRegistry portletDefinitionRegistry;
private IUserIdentityStore userIdentityStore;
private IUserFragmentSubscriptionDao userFragmentInfoDao;
private IUserInstanceManager userInstanceManager;
private IStylesheetUserPreferencesService stylesheetUserPreferencesService;
private IUserLayoutStore userLayoutStore;
private MessageSource messageSource;
private IPortletWindowRegistry portletWindowRegistry;
@Value("${org.jasig.portal.layout.dlm.remoting.addedWindowState:null}")
private String addedPortletWindowState;
private WindowState addedWindowState;
@PostConstruct
private void initAddedPortletWindowState(){
if(addedPortletWindowState!=null && !"null".equalsIgnoreCase(addedPortletWindowState) && !addedPortletWindowState.isEmpty()){
addedWindowState = new WindowState(addedPortletWindowState);
}
}
@Autowired
public void setUserLayoutStore(IUserLayoutStore userLayoutStore) {
this.userLayoutStore = userLayoutStore;
}
@Autowired
public void setStylesheetUserPreferencesService(IStylesheetUserPreferencesService stylesheetUserPreferencesService) {
this.stylesheetUserPreferencesService = stylesheetUserPreferencesService;
}
@Autowired
public void setPortletDefinitionRegistry(IPortletDefinitionRegistry portletDefinitionRegistry) {
this.portletDefinitionRegistry = portletDefinitionRegistry;
}
@Autowired
public void setUserIdentityStore(IUserIdentityStore userStore) {
this.userIdentityStore = userStore;
}
@Autowired
public void setUserFragmentInfoDao(IUserFragmentSubscriptionDao userFragmentInfoDao) {
this.userFragmentInfoDao = userFragmentInfoDao;
}
@Autowired
public void setUserInstanceManager(IUserInstanceManager userInstanceManager) {
this.userInstanceManager = userInstanceManager;
}
@Autowired
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Autowired
public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
this.portletWindowRegistry = portletWindowRegistry;
}
// default tab name
protected static final String DEFAULT_TAB_NAME = "New Tab";
/**
* Remove an element from the layout.
*
* @param request
* @param response
* @return
* @throws IOException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=removeElement")
public ModelAndView removeElement(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
IPerson per = getPerson(ui, response);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
try {
// if the element ID starts with the fragment prefix and is a folder,
// attempt first to treat it as a pulled fragment subscription
String elementId = request.getParameter("elementID");
if (elementId != null && elementId.startsWith(Constants.FRAGMENT_ID_USER_PREFIX) &&
ulm.getNode( elementId ) instanceof org.jasig.portal.layout.node.UserLayoutFolderDescription) {
removeSubscription(per, elementId, ulm);
} else {
// Delete the requested element node. This code is the same for
// all node types, so we can just have a generic action.
ulm.deleteNode(elementId);
}
ulm.saveUserLayout();
return new ModelAndView("jsonView", Collections.EMPTY_MAP);
} catch (Exception e) {
log.warn("Failed to remove element from layout", e);
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
}
/**
* Subscribe a user to a pre-formatted tab (pulled DLM fragment).
*
* @param request
* @param response
* @return
* @throws IOException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=subscribeToTab")
public ModelAndView subscribeToTab(HttpServletRequest request,
HttpServletResponse response) throws IOException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
IPerson per = getPerson(ui, response);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
// Get the fragment owner's name from the request and construct
// an IPerson object representing that user
String fragmentOwnerName = request.getParameter("sourceID");
if (StringUtils.isBlank(fragmentOwnerName)) {
log.warn("Attempted to subscribe to tab with null owner ID");
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
RestrictedPerson fragmentOwner = PersonFactory.createRestrictedPerson();
fragmentOwner.setUserName(fragmentOwnerName);
// Mark the currently-authenticated user as subscribed to this fragment.
// If an inactivated fragment registration already exists, update it
// as an active subscription. Otherwise, create a new fragment
// subscription.
IUserFragmentSubscription userFragmentInfo = userFragmentInfoDao
.getUserFragmentInfo(per, fragmentOwner);
if (userFragmentInfo == null) {
userFragmentInfo = userFragmentInfoDao.createUserFragmentInfo(per,
fragmentOwner);
} else {
userFragmentInfo.setActive(true);
userFragmentInfoDao.updateUserFragmentInfo(userFragmentInfo);
}
try {
// reload user layout and stylesheet to incorporate new DLM fragment
ulm.loadUserLayout(true);
// get the target node this new tab should be moved after
String destinationId = request.getParameter("elementID");
// get the user layout for the currently-authenticated user
int uid = userIdentityStore.getPortalUID(fragmentOwner, false);
final DistributedUserLayout userLayout = userLayoutStore.getUserLayout(per, upm.getUserProfile());
Document layoutDocument = userLayout.getLayout();
// attempt to find the new subscribed tab in the layout so we can
// move it
StringBuilder expression = new StringBuilder("//folder[@type='root']/folder[starts-with(@ID,'")
.append(Constants.FRAGMENT_ID_USER_PREFIX)
.append(uid)
.append("')]");
XPathFactory fac = XPathFactory.newInstance();
XPath xpath = fac.newXPath();
NodeList nodes = (NodeList) xpath.evaluate(expression.toString(), layoutDocument, XPathConstants.NODESET);
String sourceId = nodes.item(0).getAttributes().getNamedItem("ID").getTextContent();
ulm.moveNode(sourceId, ulm.getParentId(destinationId), destinationId);
ulm.saveUserLayout();
return new ModelAndView("jsonView", Collections.singletonMap("tabId", sourceId));
} catch (Exception e) {
log.warn("Error subscribing to fragment owned by "
+ fragmentOwnerName, e);
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
}
/**
* Move a portlet to another location on the tab.
*
* @param ulm
* @param request
* @param response
* @throws IOException
* @throws PortalException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=movePortlet")
public ModelAndView movePortlet(HttpServletRequest request, HttpServletResponse response)
throws IOException, PortalException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
IPerson per = getPerson(ui, response);
final Locale locale = RequestContextUtils.getLocale(request);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
// portlet to be moved
String sourceId = request.getParameter("sourceID");
// Either "insertBefore" or "appendAfter".
String method = request.getParameter("method");
// Target element to move the source element in front of. This parameter
// isn't actually relevant if we're appending the source element.
String destinationId = request.getParameter("elementID");
if (isTab(ulm, destinationId)) {
// if the target is a tab type node, move the portlet to
// the end of the first column
@SuppressWarnings("unchecked")
Enumeration<String> columns = ulm.getChildIds(destinationId);
if (columns.hasMoreElements()) {
ulm.moveNode(sourceId, columns.nextElement(), null);
} else {
IUserLayoutFolderDescription newColumn = new UserLayoutFolderDescription();
newColumn.setName("Column");
newColumn.setId("tbd");
newColumn
.setFolderType(IUserLayoutFolderDescription.REGULAR_TYPE);
newColumn.setHidden(false);
newColumn.setUnremovable(false);
newColumn.setImmutable(false);
// add the column to our layout
IUserLayoutNodeDescription col = ulm.addNode(newColumn,
destinationId, null);
// move the channel
ulm.moveNode(sourceId, col.getId(), null);
}
} else if (ulm.getRootFolderId().equals(
// if the target is a column type node, we need to just move the portlet
// to the end of the column
ulm.getParentId(ulm.getParentId(destinationId)))) {
ulm.moveNode(sourceId, destinationId, null);
} else {
// If we're moving this element before another one, we need
// to know what the target is. If there's no target, just
// assume we're moving it to the very end of the column.
String siblingId = null;
if (method.equals("insertBefore"))
siblingId = destinationId;
// move the node as requested and save the layout
ulm.moveNode(sourceId, ulm.getParentId(destinationId), siblingId);
}
try {
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
return new ModelAndView("jsonView",
Collections.singletonMap("response", getMessage("error.move.portlet",
"There was an issue moving this portlet, please refresh the page and try again.", locale)));
}
return new ModelAndView("jsonView",
Collections.singletonMap("response", getMessage("success.move.portlet",
"Portlet moved successfully", locale)));
}
/**
* Change the number of columns on a specified tab. In the event that the user is
* decreasing the number of columns, extra columns will be stripped from the
* right-hand side. Any channels in these columns will be moved to the bottom of
* the last preserved column.
*
* @param per
* @param upm
* @param ulm
* @param request
* @param response
* @throws IOException
* @throws PortalException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=changeColumns")
public ModelAndView changeColumns(HttpServletRequest request,
HttpServletResponse response, @RequestParam("tabId") String tabId,
@RequestParam("widths[]") String[] widths,
@RequestParam(value = "deleted[]", required = false) String[] deleted,
@RequestParam(value = "acceptor", required = false) String acceptor) throws IOException, PortalException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
IPerson per = getPerson(ui, response);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
int newColumnCount = widths.length;
// build a list of the current columns for this tab
@SuppressWarnings("unchecked")
Enumeration<String> columns = ulm.getChildIds(tabId);
List<String> columnList = new ArrayList<String>();
while (columns.hasMoreElements()) {
columnList.add(columns.nextElement());
}
int oldColumnCount = columnList.size();
Map<String, Object> model = new HashMap<String, Object>();
// if the new layout has more columns
if (newColumnCount > oldColumnCount) {
List<String> newColumnIds = new ArrayList<String>();
for (int i = columnList.size(); i < newColumnCount; i++) {
// create new column element
IUserLayoutFolderDescription newColumn = new UserLayoutFolderDescription();
newColumn.setName("Column");
newColumn.setId("tbd");
newColumn
.setFolderType(IUserLayoutFolderDescription.REGULAR_TYPE);
newColumn.setHidden(false);
newColumn.setUnremovable(false);
newColumn.setImmutable(false);
// add the column to our layout
IUserLayoutNodeDescription node = ulm.addNode(newColumn, tabId,
null);
newColumnIds.add(node.getId());
model.put("newColumnIds", newColumnIds);
columnList.add(node.getId());
}
}
// if the new layout has fewer columns
else if (deleted != null && deleted.length > 0) {
if (columnList.size() != widths.length + deleted.length) {
// TODO: error?
}
for (String columnId : deleted) {
// move all channels in the current column to the last valid column
@SuppressWarnings("unchecked")
Enumeration channels = ulm.getChildIds(columnId);
while (channels.hasMoreElements()) {
ulm.addNode(ulm.getNode((String) channels.nextElement()),
acceptor, null);
}
// delete the column from the user's layout
ulm.deleteNode(columnId);
columnList.remove(columnId);
}
}
int count = 0;
for (String columnId : columnList) {
this.stylesheetUserPreferencesService.setLayoutAttribute(request, PreferencesScope.STRUCTURE, columnId, "width", widths[count] + "%");
try {
// This sets the column attribute in memory but doesn't persist it. Comment says saves changes "prior to persisting"
Element folder = ulm.getUserLayoutDOM().getElementById(columnId);
UserPrefsHandler.setUserPreference(folder, "width", per);
} catch (Exception e) {
log.error("Error saving new column widths", e);
}
count++;
}
try {
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
}
return new ModelAndView("jsonView", model);
}
/**
* Move a tab left or right.
*
* @param per
* @param upm
* @param ulm
* @param request
* @param response
* @throws PortalException
* @throws IOException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=moveTab")
public ModelAndView moveTab(HttpServletRequest request,
HttpServletResponse response) throws IOException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
final Locale locale = RequestContextUtils.getLocale(request);
// gather the parameters we need to move a channel
String destinationId = request.getParameter("elementID");
String sourceId = request.getParameter("sourceID");
String method = request.getParameter("method");
// If we're moving this element before another one, we need
// to know what the target is. If there's no target, just
// assume we're moving it to the very end of the list.
String siblingId = null;
if ("insertBefore".equals(method))
siblingId = destinationId;
try {
// move the node as requested and save the layout
ulm.moveNode(sourceId, ulm.getParentId(destinationId), siblingId);
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Failed to move tab in user layout", e);
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView("jsonView",
Collections.singletonMap("response", getMessage("error.move.tab",
"There was an issue moving the tab, please refresh the page and try again.", locale)));
}
return new ModelAndView("jsonView",
Collections.singletonMap("response", getMessage("success.move.tab",
"Tab moved successfully", locale)));
}
@RequestMapping(method= RequestMethod.POST , params = "action=addFavorite")
public ModelAndView addFavorite(@RequestParam String channelId, HttpServletRequest request) {
//setup
IUserInstance ui = userInstanceManager.getUserInstance(request);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
IUserLayoutChannelDescription channel = new UserLayoutChannelDescription(portletDefinitionRegistry.getPortletDefinition(channelId));
final Locale locale = RequestContextUtils.getLocale(request);
//get favorite tab
String favoriteTabNodeId = FavoritesUtils.getFavoriteTabNodeId(ulm.getUserLayout());
if(favoriteTabNodeId != null) {
//add portlet to favorite tab
IUserLayoutNodeDescription node = addNodeToTab(ulm, channel, favoriteTabNodeId);
if (node == null) {
return new ModelAndView("jsonView",
Collections.singletonMap("response",
getMessage("error.add.portlet.in.tab", "Can''t add a new favorite", locale)));
}
try {
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
return new ModelAndView("jsonView",
Collections.singletonMap("response",
getMessage("error.persisting.layout.change", "Can''t add a new favorite", locale)));
}
//document success for notifications
Map<String, String> model = new HashMap<String, String>();
final String channelTitle = channel.getTitle();
model.put("response",
getMessage("favorites.added.favorite", channelTitle,
"Added " + channelTitle + " as a favorite.", locale));
model.put("newNodeId", node.getId());
return new ModelAndView("jsonView", model);
} else {
return new ModelAndView("jsonView",
Collections.singletonMap("response",
getMessage("error.finding.favorite.tab", "Can''t find favorite tab", locale)));
}
}
/**
* This method removes the channelId specified from favorites. Note that even if you pass in the layout channel id, it will always remove from the favorites.
* @param channelId The long channel ID that is used to determine which fname to remove from favorites
* @param request
* @param response
* @return returns a mav object with a response attribute for noty
* @throws IOException if it has problem reading the layout file.
*/
@RequestMapping(method= RequestMethod.POST , params = "action=removeFavorite")
public ModelAndView removeFavorite(@RequestParam String channelId, HttpServletRequest request, HttpServletResponse response) throws IOException {
UserPreferencesManager upm = (UserPreferencesManager) userInstanceManager.getUserInstance(request).getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
final Locale locale = RequestContextUtils.getLocale(request);
IPortletDefinition portletDefinition = portletDefinitionRegistry.getPortletDefinition(channelId);
if(portletDefinition != null && StringUtils.isNotBlank(portletDefinition.getFName())) {
String functionalName = portletDefinition.getFName();
List<IUserLayoutNodeDescription> favoritePortlets = FavoritesUtils.getFavoritePortlets(ulm.getUserLayout());
//search for the favorite to delete
EqualPredicate nameEqlPredicate = new EqualPredicate(functionalName);
Object result = CollectionUtils.find(favoritePortlets, new BeanPredicate("functionalName",nameEqlPredicate));
if(result != null && result instanceof UserLayoutChannelDescription) {
UserLayoutChannelDescription channelDescription = (UserLayoutChannelDescription)result;
try {
if (!ulm.deleteNode(channelDescription.getChannelSubscribeId())) {
log.warn("Error deleting the node" + channelId + "from favorites for user " + (upm.getPerson() == null ? "unknown" : upm.getPerson().getID()));
response.setStatus(HttpServletResponse.SC_ACCEPTED);
return new ModelAndView("jsonView", Collections.singletonMap("response", getMessage("error.remove.favorite", "Can''t remove favorite", locale)));
}
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
response.setStatus(HttpServletResponse.SC_ACCEPTED);
return new ModelAndView("jsonView", Collections.singletonMap("response", getMessage("error.remove.favorite", "Can''t remove favorite", locale)));
}
//document success for notifications
Map<String, String> model = new HashMap<String, String>();
model.put("response", getMessage("success.remove.portlet", "Removed from Favorites successfully", locale));
return new ModelAndView("jsonView", model);
}
}
// save the user's layout
ulm.saveUserLayout();
return new ModelAndView("jsonView", Collections.singletonMap("response", getMessage("error.finding.favorite", "Can''t find favorite", locale)));
}
/**
* Add a new channel.
*
* @param per
* @param upm
* @param ulm
* @param request
* @param response
* @throws IOException
* @throws PortalException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=addPortlet")
public ModelAndView addPortlet(HttpServletRequest request, HttpServletResponse response) throws IOException, PortalException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
// gather the parameters we need to move a channel
String destinationId = request.getParameter("elementID");
String sourceId = request.getParameter("channelID");
String method = request.getParameter("position");
IPortletDefinition definition = portletDefinitionRegistry.getPortletDefinition(sourceId);
IUserLayoutChannelDescription channel = new UserLayoutChannelDescription(definition);
IUserLayoutNodeDescription node = null;
if (isTab(ulm, destinationId)) {
node = addNodeToTab(ulm,channel, destinationId);
} else if (isColumn(ulm, destinationId)) {
// move the channel into the column
node = ulm.addNode(channel, destinationId, null);
} else {
// If we're moving this element before another one, we need
// to know what the target is. If there's no target, just
// assume we're moving it to the very end of the column.
String siblingId = null;
if (method.equals("insertBefore"))
siblingId = destinationId;
// move the node as requested and save the layout
node = ulm.addNode(channel, ulm.getParentId(destinationId),
siblingId);
}
final Locale locale = RequestContextUtils.getLocale(request);
if (node == null) {
return new ModelAndView("jsonView", Collections.singletonMap("error", getMessage("error.add.portlet.in.tab", "Can''t add a new channel", locale)));
}
String nodeId = node.getId();
try {
// save the user's layout
ulm.saveUserLayout();
if(addedWindowState!=null){
IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByFname(request, channel.getFunctionalName());
portletWindow.setWindowState(addedWindowState);
this.portletWindowRegistry.storePortletWindow(request, portletWindow);
}
} catch (Exception e) {
log.warn("Error saving layout", e);
return new ModelAndView("jsonView", Collections.singletonMap("error", getMessage("error.persisting.layout.change", "Can''t add a new channel", locale)));
}
Map<String, String> model = new HashMap<String, String>();
model.put("response", getMessage("success.add.portlet", "Added a new channel", locale));
model.put("newNodeId", nodeId);
return new ModelAndView("jsonView", model);
}
private IUserLayoutNodeDescription addNodeToTab(IUserLayoutManager ulm, IUserLayoutChannelDescription channel, String tabId) {
IUserLayoutNodeDescription node = null;
@SuppressWarnings("unchecked")
Enumeration<String> columns = ulm.getChildIds(tabId);
if (columns.hasMoreElements()) {
while (columns.hasMoreElements()) {
// attempt to add this channel to the column
node = ulm.addNode(channel, columns.nextElement(), null);
// if it couldn't be added to this column, go on and try the next
// one. otherwise, we're set.
if (node != null)
break;
}
} else {
IUserLayoutFolderDescription newColumn = new UserLayoutFolderDescription();
newColumn.setName("Column");
newColumn.setId("tbd");
newColumn.setFolderType(IUserLayoutFolderDescription.REGULAR_TYPE);
newColumn.setHidden(false);
newColumn.setUnremovable(false);
newColumn.setImmutable(false);
// add the column to our layout
IUserLayoutNodeDescription col = ulm.addNode(newColumn, tabId, null);
// add the channel
node = ulm.addNode(channel, col.getId(), null);
}
return node;
}
/**
* Update the user's preferred skin.
*
* @param per
* @param upm
* @param ulm
* @param request
* @param response
* @throws IOException
* @throws PortalException
*/
@RequestMapping(method = RequestMethod.POST, params="action=chooseSkin")
public ModelAndView chooseSkin(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String skinName = request.getParameter("skinName");
this.stylesheetUserPreferencesService.setStylesheetParameter(request, PreferencesScope.THEME, "skin", skinName);
return new ModelAndView("jsonView", Collections.EMPTY_MAP);
}
/**
* Add a new tab to the layout. The new tab will be appended to the end of the
* list and named with the BLANK_TAB_NAME variable.
*
* @param request
* @throws IOException
*/
@RequestMapping(method = RequestMethod.POST, params="action=addTab")
public ModelAndView addTab(HttpServletRequest request, HttpServletResponse response, @RequestParam("widths[]") String[] widths) throws IOException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
IPerson per = getPerson(ui, response);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
// Verify that the user has permission to add this tab
final IAuthorizationPrincipal authPrincipal = this.getUserPrincipal(per.getUserName());
if (!authPrincipal.hasPermission(ADDTAB_PERMISSION_OWNER, ADDTAB_PERMISSION_ACTIVITY, ADDTAB_PERMISSION_TARGET)) {
log.warn("Attempt to add a tab through the REST API by unauthorized user '" + per.getUserName() + "'");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return null;
}
// construct a brand new tab
String id = "tbd";
String tabName = request.getParameter("tabName");
if (StringUtils.isBlank(tabName)) tabName = DEFAULT_TAB_NAME;
IUserLayoutFolderDescription newTab = new UserLayoutFolderDescription();
newTab.setName(tabName);
newTab.setId(id);
newTab.setFolderType(IUserLayoutFolderDescription.REGULAR_TYPE);
newTab.setHidden(false);
newTab.setUnremovable(false);
newTab.setImmutable(false);
// add the tab to the layout
ulm.addNode(newTab, ulm.getRootFolderId(), null);
try {
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
}
// get the id of the newly added tab
String tabId = newTab.getId();
for (String width : widths) {
// create new column element
IUserLayoutFolderDescription newColumn = new UserLayoutFolderDescription();
newColumn.setName("Column");
newColumn.setId("tbd");
newColumn
.setFolderType(IUserLayoutFolderDescription.REGULAR_TYPE);
newColumn.setHidden(false);
newColumn.setUnremovable(false);
newColumn.setImmutable(false);
// add the column to our layout
ulm.addNode(newColumn, tabId, null);
this.stylesheetUserPreferencesService.setLayoutAttribute(request, PreferencesScope.STRUCTURE, newColumn.getId(), "width", width + "%");
try {
// This sets the column attribute in memory but doesn't persist it. Comment says saves changes "prior to persisting"
Element folder = ulm.getUserLayoutDOM().getElementById(newColumn.getId());
UserPrefsHandler.setUserPreference(folder, "width", per);
} catch (Exception e) {
log.error("Error saving new column widths", e);
}
}
// ## 'tabGroup' value (optional feature)
// Set the 'tabGroup' attribute on the folder element that describes
// this new tab; use the currently active tabGroup.
if (request.getParameter(TAB_GROUP_PARAMETER)!= null) {
String tabGroup = request.getParameter(TAB_GROUP_PARAMETER).trim();
if (log.isDebugEnabled()) {
log.debug(TAB_GROUP_PARAMETER + "=" + tabGroup);
}
if (!TAB_GROUP_DEFAULT.equals(tabGroup) && tabGroup.length() != 0) {
// Persists SSUP values to the database
this.stylesheetUserPreferencesService.setLayoutAttribute(request, PreferencesScope.STRUCTURE, tabId, TAB_GROUP_PARAMETER , tabGroup);
}
}
try {
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
}
return new ModelAndView("jsonView", Collections.singletonMap("tabId", tabId));
}
/**
* Rename a specified tab.
*
* @param request
* @throws IOException
*/
@RequestMapping(method = RequestMethod.POST, params = "action=renameTab")
public ModelAndView renameTab(HttpServletRequest request, HttpServletResponse response) throws IOException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
// element ID of the tab to be renamed
String tabId = request.getParameter("tabId");
IUserLayoutFolderDescription tab = (IUserLayoutFolderDescription) ulm
.getNode(tabId);
// desired new name
String tabName = request.getParameter("tabName");
if (!ulm.canUpdateNode(tab)) {
log.warn("Attempting to rename an immutable tab");
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
/*
* Update the tab and save the layout
*/
tab.setName(StringUtils.isBlank(tabName) ? DEFAULT_TAB_NAME : tabName);
final boolean updated = ulm.updateNode(tab);
if (updated) {
try {
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
}
//TODO why do we have to do this, shouldn't modifying the layout be enough to trigger a full re-render (layout's cache key changes)
this.stylesheetUserPreferencesService.setLayoutAttribute(request, PreferencesScope.STRUCTURE, tabId, "name", tabName);
}
Map<String, String> model = Collections.singletonMap("message", "saved new tab name");
return new ModelAndView("jsonView", model);
}
@RequestMapping(method = RequestMethod.POST, params = "action=updatePermissions")
public ModelAndView updatePermissions(HttpServletRequest request, HttpServletResponse response) throws IOException {
IUserInstance ui = userInstanceManager.getUserInstance(request);
UserPreferencesManager upm = (UserPreferencesManager) ui.getPreferencesManager();
IUserLayoutManager ulm = upm.getUserLayoutManager();
String elementId = request.getParameter("elementID");
IUserLayoutNodeDescription node = ulm.getNode(elementId);
if (node == null){
log.warn("Failed to locate node for permissions update");
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return null;
}
String deletable = request.getParameter("deletable");
if (!StringUtils.isBlank(deletable)) {
node.setDeleteAllowed(Boolean.valueOf(deletable));
}
String movable = request.getParameter("movable");
if (!StringUtils.isBlank(movable)) {
node.setMoveAllowed(Boolean.valueOf(movable));
}
String editable = request.getParameter("editable");
if (!StringUtils.isBlank(editable)) {
node.setEditAllowed(Boolean.valueOf(editable));
}
String canAddChildren = request.getParameter("addChildAllowed");
if (!StringUtils.isBlank(canAddChildren)) {
node.setAddChildAllowed(Boolean.valueOf(canAddChildren));
}
ulm.updateNode(node);
try {
// save the user's layout
ulm.saveUserLayout();
} catch (Exception e) {
log.warn("Error saving layout", e);
}
return new ModelAndView("jsonView", Collections.EMPTY_MAP);
}
protected void removeSubscription(IPerson per, String elementId, IUserLayoutManager ulm) {
// get the fragment owner's ID from the element string
String userIdString = StringUtils.substringBetween(elementId, Constants.FRAGMENT_ID_USER_PREFIX, Constants.FRAGMENT_ID_LAYOUT_PREFIX);
int userId = NumberUtils.toInt(userIdString,0);
// construct a new person object representing the fragment owner
RestrictedPerson fragmentOwner = PersonFactory.createRestrictedPerson();
fragmentOwner.setID(userId);
fragmentOwner.setUserName(userIdentityStore.getPortalUserName(userId));
// attempt to find a subscription for this fragment
IUserFragmentSubscription subscription = userFragmentInfoDao.getUserFragmentInfo(per, fragmentOwner);
// if a subscription was found, remove it's registration
if (subscription != null) {
userFragmentInfoDao.deleteUserFragmentInfo(subscription);
ulm.loadUserLayout(true);
}
// otherwise, delete the node
else {
ulm.deleteNode(elementId);
}
}
/**
* A folder is a tab if its parent element is the layout element
*
* @param folder the folder in question
* @return <code>true</code> if the folder is a tab, otherwise <code>false</code>
*/
protected boolean isTab(IUserLayoutManager ulm, String folderId)
throws PortalException {
// we could be a bit more careful here and actually check the type
return ulm.getRootFolderId().equals(ulm.getParentId(folderId));
}
protected IPerson getPerson(IUserInstance ui, HttpServletResponse response) throws IOException {
IPerson per = ui.getPerson();
if (per.isGuest()) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return null;
}
return per;
}
/**
* Syntactic sugar for safely resolving a no-args message from message bundle.
* @param key Message bundle key
* @param defaultMessage Ready-to-present message to fall back upon.
* @param locale desired locale
* @return Resolved interpolated message or defaultMessage.
*/
protected String getMessage(String key, String defaultMessage, Locale locale) {
try {
return messageSource.getMessage(key, new Object[] {}, defaultMessage, locale);
} catch (Exception e) {
// sadly, messageSource.getMessage can throw e.g. when message is ill formatted.
log.error("Error resolving message with key {}.", key, e);
return defaultMessage;
}
}
/**
* Syntactic sugar for safely resolving a one-arg message from message bundle.
* @param key Message bundle key
* @param argument dynamic value to be interpolated
* @param defaultMessage Ready-to-present message to fall back upon.
* @param locale desired locale
* @return Resolved interpolated message or defaultMessage.
*/
protected String getMessage(String key, String argument, String defaultMessage, Locale locale) {
try {
return messageSource.getMessage(key, new String[] {argument}, defaultMessage, locale);
} catch (Exception e) {
// sadly, messageSource.getMessage can throw e.g. when message is ill formatted.
log.error("Error resolving message with key {}.", key, e);
return defaultMessage;
}
}
protected IAuthorizationPrincipal getUserPrincipal(final String userName) {
final IEntity user = GroupService.getEntity(userName, IPerson.class);
if (user == null) {
return null;
}
final AuthorizationService authService = AuthorizationService.instance();
return authService.newPrincipal(user);
}
/**
* A folder is a column if its parent is a tab element
*
* @param folder the folder in question
* @return <code>true</code> if the folder is a column, otherwise <code>false</code>
*/
protected boolean isColumn(IUserLayoutManager ulm, String folderId)
throws PortalException {
return isTab(ulm, ulm.getParentId(folderId));
}
}