001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp; 029 030import org.opencms.db.CmsSubscriptionFilter; 031import org.opencms.db.CmsSubscriptionReadMode; 032import org.opencms.db.CmsVisitedByFilter; 033import org.opencms.file.CmsGroup; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsUser; 037import org.opencms.flex.CmsFlexController; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.main.OpenCms; 041import org.opencms.util.CmsStringUtil; 042 043import java.util.ArrayList; 044import java.util.Arrays; 045import java.util.Enumeration; 046import java.util.Iterator; 047import java.util.List; 048 049import javax.servlet.ServletRequest; 050import javax.servlet.http.HttpServletRequest; 051import javax.servlet.http.HttpSession; 052import javax.servlet.jsp.JspException; 053import javax.servlet.jsp.tagext.TagSupport; 054 055import org.apache.commons.logging.Log; 056 057/** 058 * Implementation of the <code><cms:usertracking/></code> tag.<p> 059 * 060 * This tag can be used to mark OpenCms files as visited or subscribe/unsubscribe them to/from users or groups.<p> 061 * 062 * It is also possible to check if single resources are visited/subscribed by the current user.<p> 063 * 064 * See also the {@link org.opencms.db.CmsSubscriptionManager} for more information about subscription or visitation.<p> 065 * 066 * @since 8.0 067 */ 068public class CmsJspTagUserTracking extends TagSupport { 069 070 /** Prefix for the visited session attributes. */ 071 public static final String SESSION_PREFIX_SUBSCRIBED = "__ocmssubscribed_"; 072 073 /** Prefix for the visited session attributes. */ 074 public static final String SESSION_PREFIX_VISITED = "__ocmsvisited_"; 075 076 /** The log object for this class. */ 077 private static final Log LOG = CmsLog.getLog(CmsJspTagUserTracking.class); 078 079 /** Serial version UID required for safe serialization. */ 080 private static final long serialVersionUID = 4253583631739670341L; 081 082 /** Static array with allowed track action values. */ 083 private static final String[] TAG_ACTIONS = { 084 "visit", // 0, default action 085 "subscribe", // 1 086 "unsubscribe", // 2 087 "checkvisited", // 3 088 "checksubscribed" // 4 089 }; 090 091 /** List of allowed track action values for more convenient lookup. */ 092 private static final List<String> TRACK_ACTIONS_LIST = Arrays.asList(TAG_ACTIONS); 093 094 /** The value of the <code>action</code> attribute. */ 095 private String m_action; 096 097 /** The value of the <code>currentuser</code> attribute. */ 098 private boolean m_currentuser; 099 100 /** The value of the <code>file</code> attribute. */ 101 private String m_file; 102 103 /** The value of the <code>group</code> attribute. */ 104 private String m_group; 105 106 /** The value of the <code>includegroups</code> attribute. */ 107 private boolean m_includegroups; 108 109 /** The value of the <code>online</code> attribute. */ 110 private boolean m_online; 111 112 /** The value of the <code>subfolder</code> attribute. */ 113 private boolean m_subfolder; 114 115 /** The value of the <code>user</code> attribute. */ 116 private String m_user; 117 118 /** 119 * Tracks an OpenCms file according to the parameters.<p> 120 * 121 * @param action the action that should be performed 122 * @param fileName the file name to track 123 * @param subFolder flag indicating if sub folders should be included 124 * @param currentUser flag indicating if the current user should be used for the tracking action 125 * @param userName the user name that should be used for the action 126 * @param includeGroups flag indicating if the given users groups should be included 127 * @param groupName the group name that should be used for the action 128 * @param req the current request 129 * 130 * @return the result of the action, usually empty except for the check actions 131 * 132 * @throws JspException in case something goes wrong 133 */ 134 public static String userTrackingTagAction( 135 String action, 136 String fileName, 137 boolean subFolder, 138 boolean currentUser, 139 String userName, 140 boolean includeGroups, 141 String groupName, 142 HttpServletRequest req) throws JspException { 143 144 String result = ""; 145 146 CmsFlexController controller = CmsFlexController.getController(req); 147 CmsObject cms = controller.getCmsObject(); 148 149 CmsUser user = null; 150 CmsGroup group = null; 151 152 int actionIndex = TRACK_ACTIONS_LIST.indexOf(action); 153 154 try { 155 // determine the group for the action 156 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(groupName)) { 157 group = cms.readGroup(groupName); 158 } 159 // determine the user for the action 160 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(userName)) { 161 user = cms.readUser(userName); 162 } else if (currentUser) { 163 user = cms.getRequestContext().getCurrentUser(); 164 } 165 if ((group == null) && (user == null) && (actionIndex != 4)) { 166 // set current user for the action except for check subscriptions 167 user = cms.getRequestContext().getCurrentUser(); 168 } 169 // determine the file to track 170 if (CmsStringUtil.isEmptyOrWhitespaceOnly(fileName)) { 171 fileName = cms.getRequestContext().getUri(); 172 } 173 174 switch (actionIndex) { 175 case 1: // subscribe 176 if (group != null) { 177 OpenCms.getSubscriptionManager().subscribeResourceFor(cms, group, fileName); 178 } 179 if (user != null) { 180 OpenCms.getSubscriptionManager().subscribeResourceFor(cms, user, fileName); 181 } 182 removeSessionAttributes(new String[] {SESSION_PREFIX_SUBSCRIBED}, req); 183 break; 184 case 2: // unsubscribe 185 if (group != null) { 186 OpenCms.getSubscriptionManager().unsubscribeResourceFor(cms, group, fileName); 187 } 188 if (user != null) { 189 OpenCms.getSubscriptionManager().unsubscribeResourceFor(cms, user, fileName); 190 } 191 removeSessionAttributes(new String[] {SESSION_PREFIX_SUBSCRIBED}, req); 192 break; 193 case 3: // checkvisited 194 result = String.valueOf(isResourceVisited(cms, fileName, subFolder, user, req)); 195 break; 196 case 4: // checksubscribed 197 List<CmsGroup> groups = new ArrayList<CmsGroup>(); 198 if ((group == null) && includeGroups) { 199 if (user != null) { 200 groups.addAll(cms.getGroupsOfUser(user.getName(), false)); 201 } else { 202 groups.addAll( 203 cms.getGroupsOfUser(cms.getRequestContext().getCurrentUser().getName(), false)); 204 } 205 } else if (group != null) { 206 groups.add(group); 207 } 208 result = String.valueOf(isResourceSubscribed(cms, fileName, subFolder, user, groups, req)); 209 break; 210 case 0: // visit 211 default: // default action is visit 212 OpenCms.getSubscriptionManager().markResourceAsVisitedBy(cms, fileName, user); 213 removeSessionAttributes(new String[] {SESSION_PREFIX_SUBSCRIBED, SESSION_PREFIX_VISITED}, req); 214 } 215 } catch (CmsException e) { 216 // store original Exception in controller in order to display it later 217 Throwable t = controller.setThrowable(e, cms.getRequestContext().getUri()); 218 throw new JspException(t); 219 } 220 221 return result; 222 } 223 224 /** 225 * Returns a unique session key depending on the values of the given parameters.<p> 226 * 227 * @param prefix the key prefix to use 228 * @param fileName the file name to track 229 * @param subFolder flag indicating if sub folders should be included 230 * @param user the user that should be used 231 * @param groups the groups that should be used 232 * 233 * @return a unique session key 234 */ 235 protected static String generateSessionKey( 236 String prefix, 237 String fileName, 238 boolean subFolder, 239 CmsUser user, 240 List<CmsGroup> groups) { 241 242 StringBuffer result = new StringBuffer(256); 243 result.append(prefix); 244 result.append(CmsResource.getFolderPath(fileName).hashCode()).append("_"); 245 result.append(subFolder); 246 if (user != null) { 247 // add user to session key 248 result.append("_").append(user.getName().hashCode()); 249 } 250 if ((groups != null) && !groups.isEmpty()) { 251 // add group(s) to session key 252 StringBuffer groupNames = new StringBuffer(128); 253 for (Iterator<CmsGroup> i = groups.iterator(); i.hasNext();) { 254 groupNames.append(i.next().getName()); 255 } 256 result.append("_").append(groupNames.toString().hashCode()); 257 } 258 return result.toString(); 259 } 260 261 /** 262 * Returns if the given resource is subscribed to the user or groups.<p> 263 * 264 * @param cms the current users context 265 * @param fileName the file name to track 266 * @param subFolder flag indicating if sub folders should be included 267 * @param user the user that should be used for the check 268 * @param groups the groups that should be used for the check 269 * @param req the current request 270 * 271 * @return <code>true</code> if the given resource is subscribed to the user or groups, otherwise <code>false</code> 272 * 273 * @throws CmsException if something goes wrong 274 */ 275 protected static boolean isResourceSubscribed( 276 CmsObject cms, 277 String fileName, 278 boolean subFolder, 279 CmsUser user, 280 List<CmsGroup> groups, 281 HttpServletRequest req) throws CmsException { 282 283 CmsResource checkResource = cms.readResource(fileName); 284 285 HttpSession session = req.getSession(true); 286 String sessionKey = generateSessionKey(SESSION_PREFIX_SUBSCRIBED, fileName, subFolder, user, groups); 287 // try to get the subscribed resources from a session attribute 288 @SuppressWarnings("unchecked") 289 List<CmsResource> subscribedResources = (List<CmsResource>)session.getAttribute(sessionKey); 290 if (subscribedResources == null) { 291 // first call, read subscribed resources and store them to session attribute 292 CmsSubscriptionFilter filter = new CmsSubscriptionFilter(); 293 filter.setParentPath(CmsResource.getFolderPath(checkResource.getRootPath())); 294 filter.setIncludeSubfolders(subFolder); 295 filter.setUser(user); 296 filter.setGroups(groups); 297 filter.setMode(CmsSubscriptionReadMode.ALL); 298 subscribedResources = OpenCms.getSubscriptionManager().readSubscribedResources(cms, filter); 299 session.setAttribute(sessionKey, subscribedResources); 300 } 301 return subscribedResources.contains(checkResource); 302 } 303 304 /** 305 * Returns if the given resource was visited by the user.<p> 306 * 307 * @param cms the current users context 308 * @param fileName the file name to track 309 * @param subFolder flag indicating if sub folders should be included 310 * @param user the user that should be used for the check 311 * @param req the current request 312 * 313 * @return <code>true</code> if the given resource was visited by the user, otherwise <code>false</code> 314 * 315 * @throws CmsException if something goes wrong 316 */ 317 protected static boolean isResourceVisited( 318 CmsObject cms, 319 String fileName, 320 boolean subFolder, 321 CmsUser user, 322 HttpServletRequest req) throws CmsException { 323 324 CmsResource checkResource = cms.readResource(fileName); 325 326 HttpSession session = req.getSession(true); 327 String sessionKey = generateSessionKey(SESSION_PREFIX_VISITED, fileName, subFolder, user, null); 328 // try to get the visited resources from a session attribute 329 @SuppressWarnings("unchecked") 330 List<CmsResource> visitedResources = (List<CmsResource>)req.getSession(true).getAttribute(sessionKey); 331 if (visitedResources == null) { 332 // first call, read visited resources and store them to session attribute 333 CmsVisitedByFilter filter = new CmsVisitedByFilter(); 334 filter.setUser(user); 335 filter.setParentPath(CmsResource.getFolderPath(checkResource.getRootPath())); 336 filter.setIncludeSubfolders(subFolder); 337 visitedResources = OpenCms.getSubscriptionManager().readResourcesVisitedBy(cms, filter); 338 session.setAttribute(sessionKey, visitedResources); 339 } 340 341 return visitedResources.contains(checkResource); 342 } 343 344 /** 345 * Removes all session attributes starting with the given prefixes.<p> 346 * 347 * @param prefixes the prefixes of the session attributes to remove 348 * @param req the current request 349 */ 350 protected static void removeSessionAttributes(String[] prefixes, HttpServletRequest req) { 351 352 HttpSession session = req.getSession(true); 353 @SuppressWarnings("unchecked") 354 Enumeration<String> en = session.getAttributeNames(); 355 while (en.hasMoreElements()) { 356 String attrKey = en.nextElement(); 357 for (int i = 0; i < prefixes.length; i++) { 358 if (attrKey.startsWith(prefixes[i])) { 359 session.removeAttribute(attrKey); 360 } 361 } 362 } 363 } 364 365 /** 366 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 367 */ 368 @Override 369 public int doStartTag() throws JspException { 370 371 ServletRequest req = pageContext.getRequest(); 372 373 // this will always be true if the page is called through OpenCms 374 if (CmsFlexController.isCmsRequest(req)) { 375 376 CmsObject cms = CmsFlexController.getCmsObject(req); 377 378 if (m_online && !cms.getRequestContext().getCurrentProject().isOnlineProject()) { 379 // the online flag is set and we are in an offline project, do not track file 380 return SKIP_BODY; 381 } 382 try { 383 String result = userTrackingTagAction( 384 m_action, 385 m_file, 386 m_subfolder, 387 m_currentuser, 388 m_user, 389 m_includegroups, 390 m_group, 391 (HttpServletRequest)req); 392 pageContext.getOut().print(result); 393 } catch (Exception ex) { 394 if (LOG.isErrorEnabled()) { 395 LOG.error(Messages.get().getBundle().key(Messages.ERR_PROCESS_TAG_1, "usertracking"), ex); 396 } 397 throw new JspException(ex); 398 } 399 } 400 return SKIP_BODY; 401 } 402 403 /** 404 * Returns the action that should be performed, i.e. mark as visited or subscribe/unsubscribe.<p> 405 * 406 * @return the action that should be performed 407 */ 408 public String getAction() { 409 410 return m_action != null ? m_action : ""; 411 } 412 413 /** 414 * Returns the current user flag.<p> 415 * 416 * @return the current user flag 417 */ 418 public String getCurrentuser() { 419 420 return String.valueOf(m_currentuser); 421 } 422 423 /** 424 * Returns the file name to track.<p> 425 * 426 * @return the file name to track 427 */ 428 public String getFile() { 429 430 return m_file != null ? m_file : ""; 431 } 432 433 /** 434 * Returns the group name that is used for the tracking.<p> 435 * 436 * @return the group name that is used for the tracking 437 */ 438 public String getGroup() { 439 440 return m_group != null ? m_group : ""; 441 } 442 443 /** 444 * Returns the include groups flag.<p> 445 * 446 * @return the include groups flag 447 */ 448 public String getIncludegroups() { 449 450 return String.valueOf(m_includegroups); 451 } 452 453 /** 454 * Returns the online flag.<p> 455 * 456 * @return the online flag 457 */ 458 public String getOnline() { 459 460 return String.valueOf(m_online); 461 } 462 463 /** 464 * Returns the subfolder flag.<p> 465 * 466 * @return the subfolder flag 467 */ 468 public String getSubfolder() { 469 470 return String.valueOf(m_subfolder); 471 } 472 473 /** 474 * Returns the user name that is used for the tracking.<p> 475 * 476 * @return the user name that is used for the tracking 477 */ 478 public String getUser() { 479 480 return m_user != null ? m_user : ""; 481 } 482 483 /** 484 * @see javax.servlet.jsp.tagext.Tag#release() 485 */ 486 @Override 487 public void release() { 488 489 super.release(); 490 m_action = null; 491 m_file = null; 492 m_group = null; 493 m_includegroups = false; 494 m_online = false; 495 m_subfolder = false; 496 m_user = null; 497 } 498 499 /** 500 * Sets the action that should be performed, i.e. mark as visited or subscribe/unsubscribe.<p> 501 * 502 * @param action the action that should be performed 503 */ 504 public void setAction(String action) { 505 506 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(action)) { 507 m_action = action; 508 } 509 } 510 511 /** 512 * Sets the current user flag.<p> 513 * 514 * Current user is <code>false</code> by default.<p> 515 * 516 * @param currentUser the flag to set 517 */ 518 public void setCurrentuser(String currentUser) { 519 520 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(currentUser)) { 521 m_currentuser = Boolean.valueOf(currentUser).booleanValue(); 522 } 523 } 524 525 /** 526 * Sets the file name to track.<p> 527 * 528 * @param file the file name to track 529 */ 530 public void setFile(String file) { 531 532 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(file)) { 533 m_file = file; 534 } 535 } 536 537 /** 538 * Sets the group name that is used for the tracking.<p> 539 * 540 * @param group the group name that is used for the tracking 541 */ 542 public void setGroup(String group) { 543 544 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(group)) { 545 m_group = group; 546 } 547 } 548 549 /** 550 * Sets the include groups flag.<p> 551 * 552 * Include groups is <code>false</code> by default.<p> 553 * 554 * @param includeGroups the flag to set 555 */ 556 public void setIncludegroups(String includeGroups) { 557 558 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(includeGroups)) { 559 m_includegroups = Boolean.valueOf(includeGroups).booleanValue(); 560 } 561 } 562 563 /** 564 * Sets the online flag.<p> 565 * 566 * Online is <code>false</code> by default.<p> 567 * 568 * @param online the flag to set 569 */ 570 public void setOnline(String online) { 571 572 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(online)) { 573 m_online = Boolean.valueOf(online).booleanValue(); 574 } 575 } 576 577 /** 578 * Sets the subfolder flag.<p> 579 * 580 * Online is <code>false</code> by default.<p> 581 * 582 * @param subfolder the flag to set 583 */ 584 public void setSubfolder(String subfolder) { 585 586 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(subfolder)) { 587 m_subfolder = Boolean.valueOf(subfolder).booleanValue(); 588 } 589 } 590 591 /** 592 * Sets the user name that is used for the tracking.<p> 593 * 594 * @param user the user name that is used for the tracking 595 */ 596 public void setUser(String user) { 597 598 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(user)) { 599 m_user = user; 600 } 601 } 602 603}