/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software GmbH, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.jsp;
import org.opencms.db.CmsSubscriptionFilter;
import org.opencms.db.CmsSubscriptionManager;
import org.opencms.db.CmsSubscriptionReadMode;
import org.opencms.db.CmsVisitedByFilter;
import org.opencms.file.CmsGroup;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsUser;
import org.opencms.flex.CmsFlexController;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsStringUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.commons.logging.Log;
/**
* Implementation of the <code><cms:usertracking/></code> tag.<p>
*
* This tag can be used to mark OpenCms files as visited or subscribe/unsubscribe them to/from users or groups.<p>
*
* It is also possible to check if single resources are visited/subscribed by the current user.<p>
*
* See also the {@link CmsSubscriptionManager} for more information about subscription or visitation.<p>
*
* @since 8.0
*/
public class CmsJspTagUserTracking extends TagSupport {
/** Prefix for the visited session attributes. */
public static final String SESSION_PREFIX_SUBSCRIBED = "__ocmssubscribed_";
/** Prefix for the visited session attributes. */
public static final String SESSION_PREFIX_VISITED = "__ocmsvisited_";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsJspTagUserTracking.class);
/** Serial version UID required for safe serialization. */
private static final long serialVersionUID = 4253583631739670341L;
/** Static array with allowed track action values. */
private static final String[] TAG_ACTIONS = {"visit", // 0, default action
"subscribe", // 1
"unsubscribe", // 2
"checkvisited", // 3
"checksubscribed" // 4
};
/** List of allowed track action values for more convenient lookup. */
private static final List<String> TRACK_ACTIONS_LIST = Arrays.asList(TAG_ACTIONS);
/** The value of the <code>action</code> attribute. */
private String m_action;
/** The value of the <code>currentuser</code> attribute. */
private boolean m_currentuser;
/** The value of the <code>file</code> attribute. */
private String m_file;
/** The value of the <code>group</code> attribute. */
private String m_group;
/** The value of the <code>includegroups</code> attribute. */
private boolean m_includegroups;
/** The value of the <code>online</code> attribute. */
private boolean m_online;
/** The value of the <code>subfolder</code> attribute. */
private boolean m_subfolder;
/** The value of the <code>user</code> attribute. */
private String m_user;
/**
* Tracks an OpenCms file according to the parameters.<p>
*
* @param action the action that should be performed
* @param fileName the file name to track
* @param subFolder flag indicating if sub folders should be included
* @param currentUser flag indicating if the current user should be used for the tracking action
* @param userName the user name that should be used for the action
* @param includeGroups flag indicating if the given users groups should be included
* @param groupName the group name that should be used for the action
* @param req the current request
*
* @return the result of the action, usually empty except for the check actions
*
* @throws JspException in case something goes wrong
*/
public static String userTrackingTagAction(
String action,
String fileName,
boolean subFolder,
boolean currentUser,
String userName,
boolean includeGroups,
String groupName,
HttpServletRequest req) throws JspException {
String result = "";
CmsFlexController controller = CmsFlexController.getController(req);
CmsObject cms = controller.getCmsObject();
CmsUser user = null;
CmsGroup group = null;
int actionIndex = TRACK_ACTIONS_LIST.indexOf(action);
try {
// determine the group for the action
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(groupName)) {
group = cms.readGroup(groupName);
}
// determine the user for the action
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(userName)) {
user = cms.readUser(userName);
} else if (currentUser) {
user = cms.getRequestContext().getCurrentUser();
}
if ((group == null) && (user == null) && (actionIndex != 4)) {
// set current user for the action except for check subscriptions
user = cms.getRequestContext().getCurrentUser();
}
// determine the file to track
if (CmsStringUtil.isEmptyOrWhitespaceOnly(fileName)) {
fileName = cms.getRequestContext().getUri();
}
switch (actionIndex) {
case 1: // subscribe
if (group != null) {
OpenCms.getSubscriptionManager().subscribeResourceFor(cms, group, fileName);
}
if (user != null) {
OpenCms.getSubscriptionManager().subscribeResourceFor(cms, user, fileName);
}
removeSessionAttributes(new String[] {SESSION_PREFIX_SUBSCRIBED}, req);
break;
case 2: // unsubscribe
if (group != null) {
OpenCms.getSubscriptionManager().unsubscribeResourceFor(cms, group, fileName);
}
if (user != null) {
OpenCms.getSubscriptionManager().unsubscribeResourceFor(cms, user, fileName);
}
removeSessionAttributes(new String[] {SESSION_PREFIX_SUBSCRIBED}, req);
break;
case 3: // checkvisited
result = String.valueOf(isResourceVisited(cms, fileName, subFolder, user, req));
break;
case 4: // checksubscribed
List<CmsGroup> groups = new ArrayList<CmsGroup>();
if ((group == null) && includeGroups) {
if (user != null) {
groups.addAll(cms.getGroupsOfUser(user.getName(), false));
} else {
groups.addAll(cms.getGroupsOfUser(cms.getRequestContext().getCurrentUser().getName(), false));
}
} else if (group != null) {
groups.add(group);
}
result = String.valueOf(isResourceSubscribed(cms, fileName, subFolder, user, groups, req));
break;
case 0: // visit
default: // default action is visit
OpenCms.getSubscriptionManager().markResourceAsVisitedBy(cms, fileName, user);
removeSessionAttributes(new String[] {SESSION_PREFIX_SUBSCRIBED, SESSION_PREFIX_VISITED}, req);
}
} catch (CmsException e) {
// store original Exception in controller in order to display it later
Throwable t = controller.setThrowable(e, cms.getRequestContext().getUri());
throw new JspException(t);
}
return result;
}
/**
* Returns a unique session key depending on the values of the given parameters.<p>
*
* @param prefix the key prefix to use
* @param fileName the file name to track
* @param subFolder flag indicating if sub folders should be included
* @param user the user that should be used
* @param groups the groups that should be used
*
* @return a unique session key
*/
protected static String generateSessionKey(
String prefix,
String fileName,
boolean subFolder,
CmsUser user,
List<CmsGroup> groups) {
StringBuffer result = new StringBuffer(256);
result.append(prefix);
result.append(CmsResource.getFolderPath(fileName).hashCode()).append("_");
result.append(subFolder);
if (user != null) {
// add user to session key
result.append("_").append(user.getName().hashCode());
}
if ((groups != null) && !groups.isEmpty()) {
// add group(s) to session key
StringBuffer groupNames = new StringBuffer(128);
for (Iterator<CmsGroup> i = groups.iterator(); i.hasNext();) {
groupNames.append(i.next().getName());
}
result.append("_").append(groupNames.toString().hashCode());
}
return result.toString();
}
/**
* Returns if the given resource is subscribed to the user or groups.<p>
*
* @param cms the current users context
* @param fileName the file name to track
* @param subFolder flag indicating if sub folders should be included
* @param user the user that should be used for the check
* @param groups the groups that should be used for the check
* @param req the current request
*
* @return <code>true</code> if the given resource is subscribed to the user or groups, otherwise <code>false</code>
*
* @throws CmsException if something goes wrong
*/
protected static boolean isResourceSubscribed(
CmsObject cms,
String fileName,
boolean subFolder,
CmsUser user,
List<CmsGroup> groups,
HttpServletRequest req) throws CmsException {
CmsResource checkResource = cms.readResource(fileName);
HttpSession session = req.getSession(true);
String sessionKey = generateSessionKey(SESSION_PREFIX_SUBSCRIBED, fileName, subFolder, user, groups);
// try to get the subscribed resources from a session attribute
List<CmsResource> subscribedResources = (List<CmsResource>)session.getAttribute(sessionKey);
if (subscribedResources == null) {
// first call, read subscribed resources and store them to session attribute
CmsSubscriptionFilter filter = new CmsSubscriptionFilter();
filter.setParentPath(CmsResource.getFolderPath(checkResource.getRootPath()));
filter.setIncludeSubfolders(subFolder);
filter.setUser(user);
filter.setGroups(groups);
filter.setMode(CmsSubscriptionReadMode.ALL);
subscribedResources = OpenCms.getSubscriptionManager().readSubscribedResources(cms, filter);
session.setAttribute(sessionKey, subscribedResources);
}
return subscribedResources.contains(checkResource);
}
/**
* Returns if the given resource was visited by the user.<p>
*
* @param cms the current users context
* @param fileName the file name to track
* @param subFolder flag indicating if sub folders should be included
* @param user the user that should be used for the check
* @param req the current request
*
* @return <code>true</code> if the given resource was visited by the user, otherwise <code>false</code>
*
* @throws CmsException if something goes wrong
*/
protected static boolean isResourceVisited(
CmsObject cms,
String fileName,
boolean subFolder,
CmsUser user,
HttpServletRequest req) throws CmsException {
CmsResource checkResource = cms.readResource(fileName);
HttpSession session = req.getSession(true);
String sessionKey = generateSessionKey(SESSION_PREFIX_VISITED, fileName, subFolder, user, null);
// try to get the visited resources from a session attribute
List<CmsResource> visitedResources = (List<CmsResource>)req.getSession(true).getAttribute(sessionKey);
if (visitedResources == null) {
// first call, read visited resources and store them to session attribute
CmsVisitedByFilter filter = new CmsVisitedByFilter();
filter.setUser(user);
filter.setParentPath(CmsResource.getFolderPath(checkResource.getRootPath()));
filter.setIncludeSubfolders(subFolder);
visitedResources = OpenCms.getSubscriptionManager().readResourcesVisitedBy(cms, filter);
session.setAttribute(sessionKey, visitedResources);
}
return visitedResources.contains(checkResource);
}
/**
* Removes all session attributes starting with the given prefixes.<p>
*
* @param prefixes the prefixes of the session attributes to remove
* @param req the current request
*/
protected static void removeSessionAttributes(String[] prefixes, HttpServletRequest req) {
HttpSession session = req.getSession(true);
Enumeration<String> en = session.getAttributeNames();
while (en.hasMoreElements()) {
String attrKey = en.nextElement();
for (int i = 0; i < prefixes.length; i++) {
if (attrKey.startsWith(prefixes[i])) {
session.removeAttribute(attrKey);
}
}
}
}
/**
* @see javax.servlet.jsp.tagext.Tag#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
ServletRequest req = pageContext.getRequest();
// this will always be true if the page is called through OpenCms
if (CmsFlexController.isCmsRequest(req)) {
CmsObject cms = CmsFlexController.getCmsObject(req);
if (m_online && !cms.getRequestContext().getCurrentProject().isOnlineProject()) {
// the online flag is set and we are in an offline project, do not track file
return SKIP_BODY;
}
try {
String result = userTrackingTagAction(
m_action,
m_file,
m_subfolder,
m_currentuser,
m_user,
m_includegroups,
m_group,
(HttpServletRequest)req);
pageContext.getOut().print(result);
} catch (Exception ex) {
if (LOG.isErrorEnabled()) {
LOG.error(Messages.get().getBundle().key(Messages.ERR_PROCESS_TAG_1, "usertracking"), ex);
}
throw new JspException(ex);
}
}
return SKIP_BODY;
}
/**
* Returns the action that should be performed, i.e. mark as visited or subscribe/unsubscribe.<p>
*
* @return the action that should be performed
*/
public String getAction() {
return m_action != null ? m_action : "";
}
/**
* Returns the current user flag.<p>
*
* @return the current user flag
*/
public String getCurrentuser() {
return String.valueOf(m_currentuser);
}
/**
* Returns the file name to track.<p>
*
* @return the file name to track
*/
public String getFile() {
return m_file != null ? m_file : "";
}
/**
* Returns the group name that is used for the tracking.<p>
*
* @return the group name that is used for the tracking
*/
public String getGroup() {
return m_group != null ? m_group : "";
}
/**
* Returns the include groups flag.<p>
*
* @return the include groups flag
*/
public String getIncludegroups() {
return String.valueOf(m_includegroups);
}
/**
* Returns the online flag.<p>
*
* @return the online flag
*/
public String getOnline() {
return String.valueOf(m_online);
}
/**
* Returns the subfolder flag.<p>
*
* @return the subfolder flag
*/
public String getSubfolder() {
return String.valueOf(m_subfolder);
}
/**
* Returns the user name that is used for the tracking.<p>
*
* @return the user name that is used for the tracking
*/
public String getUser() {
return m_user != null ? m_user : "";
}
/**
* @see javax.servlet.jsp.tagext.Tag#release()
*/
@Override
public void release() {
super.release();
m_action = null;
m_file = null;
m_group = null;
m_includegroups = false;
m_online = false;
m_subfolder = false;
m_user = null;
}
/**
* Sets the action that should be performed, i.e. mark as visited or subscribe/unsubscribe.<p>
*
* @param action the action that should be performed
*/
public void setAction(String action) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(action)) {
m_action = action;
}
}
/**
* Sets the current user flag.<p>
*
* Current user is <code>false</code> by default.<p>
*
* @param currentUser the flag to set
*/
public void setCurrentuser(String currentUser) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(currentUser)) {
m_currentuser = Boolean.valueOf(currentUser).booleanValue();
}
}
/**
* Sets the file name to track.<p>
*
* @param file the file name to track
*/
public void setFile(String file) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(file)) {
m_file = file;
}
}
/**
* Sets the group name that is used for the tracking.<p>
*
* @param group the group name that is used for the tracking
*/
public void setGroup(String group) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(group)) {
m_group = group;
}
}
/**
* Sets the include groups flag.<p>
*
* Include groups is <code>false</code> by default.<p>
*
* @param includeGroups the flag to set
*/
public void setIncludegroups(String includeGroups) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(includeGroups)) {
m_includegroups = Boolean.valueOf(includeGroups).booleanValue();
}
}
/**
* Sets the online flag.<p>
*
* Online is <code>false</code> by default.<p>
*
* @param online the flag to set
*/
public void setOnline(String online) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(online)) {
m_online = Boolean.valueOf(online).booleanValue();
}
}
/**
* Sets the subfolder flag.<p>
*
* Online is <code>false</code> by default.<p>
*
* @param subfolder the flag to set
*/
public void setSubfolder(String subfolder) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(subfolder)) {
m_subfolder = Boolean.valueOf(subfolder).booleanValue();
}
}
/**
* Sets the user name that is used for the tracking.<p>
*
* @param user the user name that is used for the tracking
*/
public void setUser(String user) {
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(user)) {
m_user = user;
}
}
}