Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2013 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.adobe.cq.social.commons.comments.endpoints;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlException;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.activation.DataSource;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.request.RequestParameterMap;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.cq.social.commons.CollabUser;
import com.adobe.cq.social.commons.CollabUtil;
import com.adobe.cq.social.commons.Comment;
import com.adobe.cq.social.commons.CommentException;
import com.adobe.cq.social.commons.CommentSystem;
import com.adobe.cq.social.commons.CommentUtil;
import com.adobe.cq.social.commons.FileUploadSizeLimit;
import com.adobe.cq.social.commons.bundleactivator.Activator;
import com.adobe.cq.social.commons.comments.scheduler.api.ScheduledPostService;
import com.adobe.cq.social.commons.comments.states.internal.State;
import com.adobe.cq.social.commons.comments.states.internal.StateMachine;
import com.adobe.cq.social.commons.events.CommentEvent;
import com.adobe.cq.social.commons.ugclimiter.api.UGCLimiterService;
import com.adobe.cq.social.scf.InheritedOperationExtensionManager;
import com.adobe.cq.social.scf.Operation;
import com.adobe.cq.social.scf.OperationException;
import com.adobe.cq.social.scf.OperationExtension;
import com.adobe.cq.social.scf.SocialComponent;
import com.adobe.cq.social.scf.SocialComponentFactoryManager;
import com.adobe.cq.social.scf.core.SocialEvent;
import com.adobe.cq.social.scf.core.operations.AbstractOperationService;
import com.adobe.cq.social.srp.SocialResourceProvider;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.adobe.cq.social.ugcbase.core.SocialResourceUtils;
import com.adobe.cq.social.ugcbase.core.attachments.AttachmentUtils;
import com.adobe.cq.social.graph.SocialGraph;
import com.adobe.granite.socialgraph.Direction;
import com.adobe.granite.socialgraph.Relationship;
import com.adobe.cq.social.graph.Vertex;
import com.adobe.granite.security.user.UserProperties;
import com.day.cq.commons.Externalizer;
import com.day.cq.commons.date.DateUtil;
import com.day.cq.commons.date.InvalidDateException;
import com.day.cq.wcm.api.NameConstants;
import com.day.text.Text;
/**
* Provides abstract implementation of comment operations. This class can be extended to implement
* create/delete/update operations for any component that extends the comment system.
* @param is a {@link OperationExtension} that will be used as hooks by the extending class.
* @param is a {@link Operation} that is being provided by the extending class.
*/
@Component(metatype = false, componentAbstract = true)
@Properties({
@Property(name = AbstractCommentOperationService.PROPERTY_FIELD_WHITELIST, value = {"cq:tags", "tags",
"composedFor"}, cardinality = 100),
/*
* NOTE: the single value DEFAULT array here is special and activates the new service to provide the value by
* default, but allows existing configurations to override the value.
*/
@Property(name = AbstractCommentOperationService.PROPERTY_ATTACHMENT_TYPE_BLACKLIST,
cardinality = Integer.MAX_VALUE, value = {"DEFAULT"})})
public abstract class AbstractCommentOperationService
extends AbstractOperationService {
private static final String CQ_TAGS_PROPERTY = "cq:tags";
/** The Constant CHARSET_PROPERTY. */
public static final String CHARSET_PROPERTY = "_charset_";
/** The Constant TAGS_PROPERTY. */
public static final String TAGS_PROPERTY = "tags";
/**
* The name of the property that holds a white list of form field names added as additional properties to the
* comment.
*/
public static final String PROPERTY_FIELD_WHITELIST = "fieldWhitelist";
private static final String MODERATORS_ROLE = "moderators";
private static final String OWNER_ROLE = "owner";
private static final String PROPERTY_STATE = "state_s";
private static final String PROPERTY_TOSTATE = "toState";
private static final String PROPERTY_ISCREATEDBYPRIVUSER = "isCreatedByPrivilegedUser";
private static final String PROPERTY_STATE_OPERATION = "stateOperation";
private static final String PROPERTY_ADD_REPLY = "addReply";
private static final String DRAFT_STATE = "Draft";
private static final String SUBMITTED_STATE = "Submitted";
private static final String ANONYMOUS = "anonymous";
private static final String IGNORE_EVENT_POST = "ignoreEventPost";
/**
* The property name for allowing all members when allowPrivileged is enabled.
*/
private static final String PN_ALLOW_ALL_MEMBERS = "allowAllMembers";
//Subscription Types
private static final String NOTIFICATION = "notification";
private static final String SUBSCRIPTION = "subscription";
private static final String ACTIVITYSTREAMS = "following";
/**
* The default property value for allowing all members when allowPrivileged is enabled.
*/
private static final boolean PV_DEFAULT_ALLOW_ALL_MEMBERS = false;
/**
* List of properties that the comment will overwrite.
*/
public static final String[] RESERVED_PROPERTY_NAMES = {CollabUser.PROP_EMAIL, CollabUser.PROP_NAME,
CollabUser.PROP_WEBSITE};
/**
* The name of the property that holds a black list of attachment mime types that are unsafe.
*/
public static final String PROPERTY_ATTACHMENT_TYPE_BLACKLIST = "attachmentTypeBlacklist";
/**
* The name of the property that holds the comment message.
*/
public static final String PROP_MESSAGE = "message";
private static final String PN_COMPOSED_FOR = "composedFor";
private static final String PARAM_IS_MESSAGE_ENCODED = "messageEncoded";
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(AbstractCommentOperationService.class);
private static final String PROP_ATT_TO_REMOVE = "attToRemove";
public static final String PROP_CONTEXT_PATH = "contextPath";
@Reference
public SocialComponentFactoryManager componentFactoryManager;
@Reference
protected ScheduledPostService futurePostScheduler;
/**
* Resource Resolver used for the request.
*/
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.STATIC)
protected ResourceResolverFactory resourceResolverFactory;
/**
* Resource Resolver used for the request.
*/
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.STATIC)
protected Externalizer externalizer;
/**
* Sling settings service.
*/
@Reference
protected SlingSettingsService settingsService;
@Reference
protected EventAdmin eventAdmin;
/**
* UGC Limiter Service for checking contribution limits .
*/
@Reference
protected UGCLimiterService ugcLimiterService;
/**
* Sling repository for finding / creating nodes.
*/
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.STATIC)
protected SlingRepository repository;
@Reference
protected InheritedOperationExtensionManager extensionManager;
/**
* White list field.
*/
protected String[] fieldWhitelist;
/**
* Attachment type blacklist.
*/
protected String[] attachmentTypeBlacklist;
@SuppressWarnings("unused")
private ComponentContext context;
@Override
public InheritedOperationExtensionManager getInheritedOperationExtensionManager() {
return extensionManager;
}
/**
* Extract the default comment properties from the specified {@link SlingHttpServletRequest} into the specified.
* {@link Map}
* @param request SlingHttpServletRequest
* @param props Map
* @param session Session
* @throws RepositoryException RepositoryException
* @throws OperationException OperationException
*/
protected void getDefaultProperties(final SlingHttpServletRequest request, final Map props,
final Session session) throws RepositoryException, OperationException {
final Resource resource = request.getResource();
final String name = getAuthorizableId(request, session);
final CommentSystem cs = getCommentSystem(resource, session);
final com.adobe.cq.social.commons.Comment comment = getComment(resource, session);
//IsCreatedByPrivlegedUser
final Boolean isCreatedByPrivilegedUser = CommentUtil.isUserPrivileged(cs.getResource(), resource.getResourceResolver(), name);
props.put(PROPERTY_ISCREATEDBYPRIVUSER, isCreatedByPrivilegedUser);
props.put(PROP_CONTEXT_PATH, request.getContextPath());
// email
String email = request.getParameter(com.adobe.cq.social.ugcbase.CollabUser.PROP_EMAIL);
if (email == null) {
email = "";
}
props.put(com.adobe.cq.social.ugcbase.CollabUser.PROP_EMAIL, email);
// website
String website = request.getParameter(com.adobe.cq.social.ugcbase.CollabUser.PROP_WEBSITE);
if (website == null) {
website = "";
} else if (!"".equals(website) && !website.matches("^.*\\:\\/\\/.*$")) {
website = "http://" + website;
}
props.put(com.adobe.cq.social.ugcbase.CollabUser.PROP_WEBSITE, website);
// ip
props.put(com.adobe.cq.social.commons.Comment.PROP_IP_ADDRESS, getClientIpAddr(request));
// userAgent
final String userAgent = request.getHeader("User-Agent");
if (StringUtils.isNotBlank(userAgent)) {
props.put(com.adobe.cq.social.commons.Comment.PROP_USER_AGENT, request.getHeader("User-Agent"));
}
// authorizable id
final String authId = getAuthorizableId(request, session);
if (StringUtils.isNotBlank(authId)) {
props.put("authorizableId", authId);
}
// referer
getReferrerProperty(request, props);
// isMessageEncoded and wouldnt be saved in UGC
final String isMsgEncoded = request.getParameter(PARAM_IS_MESSAGE_ENCODED);
if (StringUtils.isNotBlank(isMsgEncoded)) {
props.put(PARAM_IS_MESSAGE_ENCODED, Boolean.parseBoolean(isMsgEncoded));
}
// the message
final String value = request.getParameter(PROP_MESSAGE);
if (StringUtils.isNotBlank(value)) {
props.put(PROP_MESSAGE, value);
}
// isDraft
final String isDraft = request.getParameter(Comment.PROP_IS_DRAFT);
if (StringUtils.isNotBlank(isDraft)) {
props.put(Comment.PROP_IS_DRAFT, Boolean.parseBoolean(isDraft));
// initial state
// TODO: should be done by state machine
props.put(PROPERTY_STATE, Boolean.parseBoolean(isDraft) ? DRAFT_STATE : SUBMITTED_STATE);
}
// check publish later properties
final String isPublishLater = request.getParameter(Comment.PROP_IS_SCHEDULED);
if (StringUtils.isNotBlank(isPublishLater)) {
// publishDate
final String publishDateString = request.getParameter(Comment.PROP_PUBLISH_DATE);
if (StringUtils.isNotBlank(publishDateString)) {
try {
Calendar publishDate;
publishDate = DateUtil.parseISO8601(publishDateString, TimeZone.getTimeZone("Etc/UTC"));
if (publishDate != null) {
props.put(Comment.PROP_PUBLISH_DATE, publishDate);
} else {
throw new OperationException("Publish date and time format is incorrect",
HttpServletResponse.SC_BAD_REQUEST);
}
} catch (final InvalidDateException e) {
LOG.error("Invalid date format for publishDate %s while trying to create/update comment",
publishDateString);
throw new OperationException("Publish date and time format is incorrect",
HttpServletResponse.SC_BAD_REQUEST);
}
props.put(Comment.PROP_IS_SCHEDULED, Boolean.parseBoolean(isPublishLater));
} else {
throw new OperationException("Publish date is empty", HttpServletResponse.SC_BAD_REQUEST);
}
}
// Check for UGC_URL
final String ugcUrl = request.getParameter(Comment.PROP_UGC_URL);
if (StringUtils.isNotBlank(ugcUrl)) {
props.put(Comment.PROP_UGC_URL, ugcUrl);
}
}
private void getReferrerProperty(final SlingHttpServletRequest request, final Map props)
throws RepositoryException {
final String referrerUrl = getReferrer(request);
if (StringUtils.isNotBlank(referrerUrl)) {
props.put(SocialComponent.PROP_REFERER, referrerUrl);
// Use the request header value if it exists, otherwise, use the request parameter.
try {
final URL url = new URL(referrerUrl);
props.put("Referer", url.getFile()); // only store relative path with parameters
} catch (final MalformedURLException e) {
// Still throw an error if the url starts with the regex.
if (referrerUrl.matches("^https?:")) {
LOG.error("Error parsing referer url", e);
} else if (StringUtils.startsWith(referrerUrl, "/")) {
// If the url doesn't start with http then we can just try and pass back the value we see,
// which in the tests was an absolute path, e.g. /whatever/this/thingy/is.html
LOG.info("Referrer URL does not have a protocol, returning passed in value {}", referrerUrl);
props.put("Referer", referrerUrl);
} else {
LOG.error("Error parsing referer url", e);
}
}
}
}
private String getClientIpAddr(final SlingHttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
private void addProp(final Map props, final String name, final SlingHttpServletRequest request,
final Session session) throws RepositoryException {
final String value = request.getParameter(name);
if (StringUtils.isNotBlank(value)) {
props.put(name, value);
}
}
/**
* Parse all the comment properties in the specified {@link SlingHttpServletRequest} and stored them in the
* specified {@link Map}.
* @param request the client http request
* @param map the map used to store the comment properties
* @param session the user session
* @throws RepositoryException if there is an error occurs
*/
protected void getCustomProperties(final SlingHttpServletRequest request, final Map map,
final Session session) throws RepositoryException {
final RequestParameterMap params = request.getRequestParameterMap();
for (final String key : params.keySet()) {
if (map.containsKey(key)) {
continue;
}
if (StringUtils.equals(key, CHARSET_PROPERTY)) {
continue;
}
// Block all properties with a colon to help mitigate CQ5-24037.
if (!ArrayUtils.contains(fieldWhitelist, key) || key.contains(":")) {
LOG.debug("skipped custom form field [{}], not in white list.", key);
continue;
}
if (!ArrayUtils.contains(RESERVED_PROPERTY_NAMES, key)) {
final RequestParameter[] values = params.get(key);
if (values.length > 0 && values[0].isFormField()) {
final Object value =
(values.length == 1) ? values[0].getString() : request.getParameterValues(key);
if (null != value) {
if (key.equals(com.adobe.cq.social.ugcbase.CollabUser.PROP_NAME)
&& ((String) value).length() == 0) {
LOG.debug("skipped custom form field \"userIdentifier\", empty value is not allowed.");
continue;
}
map.put(key, value);
}
} else {
LOG.debug("skipped custom form field [{}], empty or binary not allowed.", key);
}
} else {
LOG.debug("skipped custom form field [{}], matches reserved field name.", key);
}
}
}
@SuppressWarnings("unchecked")
protected List filterAttachments(final List attachments, final CommentSystem cs) {
if (BooleanUtils.toBoolean(cs.allowsAttachment())) {
final List attList =
(List) (List) attachments;
final Set whitelist =
cs.getAllowedFileTypes() == null ? null : new HashSet(cs.getAllowedFileTypes());
final Iterable filtered =
AttachmentUtils.getAttachmentsFromDataSources(attList,
new FileUploadSizeLimit(cs.getAttachmentSizeLimit(), cs.getAttachmentSizeLimit()), whitelist,
attachmentTypeBlacklist);
final List filteredAttList = IteratorUtils.toList(filtered.iterator());
return new ArrayList(filteredAttList);
}
return Collections.emptyList();
}
protected List getAttachmentsFromRequest(final SlingHttpServletRequest request, final CommentSystem cs) {
if (BooleanUtils.toBoolean(cs.allowsAttachment())) {
final RequestParameter[] fileRequestParameters = request.getRequestParameters("file");
return CollabUtil.getAttachmentsFromRequest(fileRequestParameters, cs.getAttachmentSizeLimit(),
cs.getAllowedFileTypes(), attachmentTypeBlacklist);
}
return Collections.emptyList();
}
public void removeAttachment(final Resource attachment, final ResourceResolver serviceUserResolver) {
if (attachment != null) {
Resource target = attachment;
final SocialUtils socialUtils = attachment.getResourceResolver().adaptTo(SocialUtils.class);
if (!socialUtils.hasModeratePermissions(attachment)
|| !socialUtils.hasModeratePermissions(attachment.getParent())) {
target = serviceUserResolver.getResource(attachment.getPath());
}
final Resource commentResource = target.getParent();
final Comment comment = commentResource.adaptTo(Comment.class);
if (comment != null) {
comment.removeAttachment(attachment.getName());
}
}
}
/**
* Return if it is in author mode.
* @return if it is in author mode.
*/
protected boolean isAuthorMode() {
return settingsService != null && settingsService.getRunModes().contains("author");
}
/**
* Checks if the request might have come from a bot.
* @param request request to check for bot
* @return if the request originated from a bot
*/
protected boolean isBot(final SlingHttpServletRequest request) {
final String botCheck = request.getParameter(com.adobe.cq.social.commons.Comment.PARAM_BOTCHECK);
return botCheck == null || !botCheck.equals(com.adobe.cq.social.commons.Comment.VALUE_BOTCHECK);
}
/**
* Retrieves the {@link ResourceResolver} based on the session.
* @param session The {@link Session} for which to get a ResourceResolver.
* @return The resource resolver.
* @throws LoginException LoginException
*/
protected ResourceResolver getResourceResolver(final Session session) throws LoginException {
final Map authInfo = new HashMap();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
return resourceResolverFactory.getResourceResolver(authInfo);
}
/**
* Get the comment system for the given resource.
* @param r The resource for which to retrieve the comment system.
* @param session The {@link Session}.
* @return The {@link CommentSystem}.
*/
protected CommentSystem getCommentSystem(final Resource r, final Session session) {
Resource res;
try {
res = getResourceResolver(session).resolve(r.getPath());
} catch (final LoginException e) {
LOG.error("Unable to fetch resource resolver for session", e);
return null;
}
if (null != res) {
return res.adaptTo(CommentSystem.class);
} else {
return null;
}
}
/**
* Get the comment for the given resource.
* @param r The resource for which to retrieve the comment.
* @param session The {@link Session}.
* @return The {@link Comment}.
*/
protected Comment getComment(final Resource r, final Session session) {
Resource res;
try {
res = getResourceResolver(session).resolve(r.getPath());
} catch (final LoginException e) {
LOG.error("Unable to fetch resource resolver for session", e);
return null;
}
if (null != res) {
return res.adaptTo(Comment.class);
} else {
return null;
}
}
/**
* Get the use id from the specified {@link SlingHttpServletRequest}; the method also validates if the session
* user id and the user id specified in the {@link SlingHttpServletRequest} is the same.
* @param request SlingHttpServletRequest
* @param session Session
* @return the authorized id
* @throws OperationException OperationException
*/
protected String getAuthorizableId(final SlingHttpServletRequest request, final Session session)
throws OperationException {
String userIdentifier = request.getParameter(com.adobe.cq.social.ugcbase.CollabUser.PROP_NAME);
if (StringUtils.isBlank(userIdentifier)) {
userIdentifier = getUserIdFromRequest(request, com.adobe.cq.social.ugcbase.CollabUser.ANONYMOUS);
}
final String sessionUserId = getUserIdFromRequest(request, null);
String id = "";
if (StringUtils.isNotBlank(sessionUserId)) {
final boolean anonymous = (com.adobe.cq.social.ugcbase.CollabUser.ANONYMOUS).equals(sessionUserId);
final boolean authorMode = isAuthorMode();
if (!anonymous && authorMode) {
final boolean userExists = userExists(userIdentifier, getRequestSession(request));
final boolean hasPermissions = hasPermissions(userIdentifier, getRequestSession(request), session);
// use node.getSession() because that's an admin session
if (userExists && hasPermissions) {
id = userIdentifier;
if (!userIdentifier.equals(sessionUserId)) {
LOG.warn(
"host {} posted a comment with different userIdentifier ({}) than sessionUserId ({})",
new String[]{request.getRemoteAddr(), userIdentifier, sessionUserId});
}
} else {
LOG.warn("host {} posted a comment with an unknown userIdentifier ({})", request.getRemoteAddr(),
userIdentifier);
}
} else if (!anonymous && !authorMode) {
final String userId = sessionUserId;
if (userIdentifier != null && !sessionUserId.equals(userIdentifier)) {
final StringBuilder exception = new StringBuilder("host ");
exception.append(request.getRemoteAddr());
exception.append("posted a comment with suspect userIdentifier (");
exception.append(userIdentifier);
exception.append("), sessionUserId (");
exception.append(sessionUserId);
exception.append(")");
final String exceptionMessage = exception.toString();
if (LOG.isWarnEnabled()) {
LOG.warn(exceptionMessage);
}
throw new OperationException(exceptionMessage, HttpServletResponse.SC_NOT_FOUND);
}
id = userId;
} else {
id = ANONYMOUS;
}
}
return id;
}
/**
* @param request SlingHttpServletRequest
* @param defaultValue String
* @return String user id
*/
protected String getUserIdFromRequest(final SlingHttpServletRequest request, final String defaultValue) {
String userIdentifier;
final UserProperties up = request.getResourceResolver().adaptTo(UserProperties.class);
userIdentifier = (up == null) ? null : up.getAuthorizableID();
if (userIdentifier == null) {
userIdentifier = defaultValue;
}
return userIdentifier;
}
/**
* Return session userProperties.
* @param request The sling request
* @return session userProperties
*/
protected UserProperties getSessionUserProperties(final SlingHttpServletRequest request) {
return request.getResourceResolver().adaptTo(UserProperties.class);
}
/**
* Return request session.
* @param request The sling request
* @return request session.
*/
protected Session getRequestSession(final SlingHttpServletRequest request) {
return request.getResourceResolver().adaptTo(Session.class);
}
/**
* Return if user exists.
* @param userId The user id
* @param session The {@link Session}.
* @return if user exists
*/
protected boolean userExists(final String userId, final Session session) {
try {
final ResourceResolver resourceResolver = getResourceResolver(session);
final UserManager userManager = resourceResolver.adaptTo(UserManager.class);
final Authorizable user = userManager.getAuthorizable(userId);
if (user != null) {
return true;
}
} catch (final RepositoryException e) {
LOG.debug("Error checking for user existence", e);
} catch (final LoginException e) {
LOG.debug("Error checking for user existence", e);
}
return false;
}
protected boolean isUserPrivileged(final CommentSystem cs, final Session session, final String userId) {
ResourceResolver resolver = null;
try {
resolver = getResourceResolver(session);
} catch (final LoginException e) {
LOG.debug("Could not check for user privileges", e);
}
Boolean isUserPrivileged = CommentUtil.isUserPrivileged(cs.getResource(), resolver, userId);
if (!isUserPrivileged && CommentUtil.getProperty(cs.getResource(), PN_ALLOW_ALL_MEMBERS,
PV_DEFAULT_ALLOW_ALL_MEMBERS)) {
return true;
}
return isUserPrivileged;
}
/**
* Return if user has permission.
* @param userIdentifier The user id
* @param requestSession The {@link Session}.
* @param adminSession The administrator {@link Session}.
* @return if user has permission.
*/
protected boolean hasPermissions(final String userIdentifier, final Session requestSession,
final Session adminSession) {
try {
if (StringUtils.isNotBlank(userIdentifier)) {
final UserProperties userProperties =
getResourceResolver(requestSession).adaptTo(UserProperties.class);
if (requestSession != null) {
return requestSession.hasPermission(userProperties.getResource(".").getPath(),
Session.ACTION_READ);
}
}
return false;
} catch (final LoginException e) {
return false;
} catch (final RepositoryException e) {
return false;
}
}
/**
* Activate this component. Open the session and register event listeners.
* @param context The component context
*/
@Activate
protected void activate(final ComponentContext context) {
this.context = context;
fieldWhitelist = OsgiUtil.toStringArray(context.getProperties().get(PROPERTY_FIELD_WHITELIST));
attachmentTypeBlacklist =
OsgiUtil.toStringArray(context.getProperties().get(PROPERTY_ATTACHMENT_TYPE_BLACKLIST));
}
/**
* Obtain the Sling repository.
* @return The repository used by this service.
*/
protected SlingRepository getRepository() {
return repository;
}
/**
* Retrieves a {@link Node} identified by the given path.
* @param path The path.
* @param session The {@link Session}.
* @return The node.
*/
protected Resource getResource(final String path, final Session session) {
try {
return getResourceResolver(session).getResource(path);
} catch (final LoginException e) {
LOG.error("Error getching the resource resolver from session", e);
return null;
}
}
protected void postEvent(final SocialEvent event) {
final EventAdmin localEventAdminRef = eventAdmin;
if (null != localEventAdminRef) {
localEventAdminRef.postEvent(event);
}
}
protected Resource create(final Resource targetCommentSystemResource, final CommentSystem cs,
final String author, final Map props, final List attachments,
final Session session) throws OperationException {
final U createOperation = getCreateOperation();
performBeforeActions(createOperation, session, targetCommentSystemResource, props);
if (cs == null) {
throw new OperationException("Failed to get comment system for target '"
+ targetCommentSystemResource.getPath() + "' ", HttpServletResponse.SC_NOT_FOUND);
}
if (cs.isClosed()) {
throw new OperationException("Reply attempted on closed comment system: " + cs.getPath(),
HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
String message;
try {
message = getStringProperty(PROP_MESSAGE, props);
} catch (final RepositoryException e) {
throw new OperationException("Failed to get the new message value", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
if (message == null || "".equals(message)) {
// These messages now flow all the way into the UX, so be careful referencing more technical names
throw new OperationException("Comment value is empty", HttpServletResponse.SC_BAD_REQUEST);
}
// If this is a reply, and the parent comment is closed, throw an exception
final Comment parent = targetCommentSystemResource.adaptTo(Comment.class);
if (parent != null && parent.isClosed()) {
throw new OperationException("Reply attempted on closed comment: "
+ targetCommentSystemResource.getPath(), HttpServletResponse.SC_BAD_REQUEST);
}
if (parent != null && !mayReply(targetCommentSystemResource, cs)) {
throw new OperationException("Reply is not allowed: " + targetCommentSystemResource.getPath(),
HttpServletResponse.SC_FORBIDDEN);
}
final long messageCharacterLimit = cs.getMessageCharacterLimit();
final String normalizedMessage = Normalizer.normalize(message, Normalizer.Form.NFC);
if (normalizedMessage.codePointCount(0, normalizedMessage.length()) > messageCharacterLimit) {
throw new OperationException("Parameter " + PROP_MESSAGE + " exceeded character limit",
HttpServletResponse.SC_BAD_REQUEST);
}
// check if UGC exists
final boolean rootPathExists = getResource(cs.getRootPath(), session) != null;
if (props.containsKey(PROP_MESSAGE)) {
props.put(com.adobe.cq.social.commons.Comment.PROP_MESSAGE, props.get(PROP_MESSAGE));
props.remove(PROP_MESSAGE);
}
if (props.containsKey(TAGS_PROPERTY)) {
props.put(CQ_TAGS_PROPERTY, props.get(TAGS_PROPERTY));
props.remove(TAGS_PROPERTY);
}
if (props.containsKey(Comment.PROP_PUBLISH_DATE)) {
if (props.get(Comment.PROP_PUBLISH_DATE) instanceof Calendar) {
final Calendar d = Calendar.getInstance();
d.setTime(((Calendar) props.get(Comment.PROP_PUBLISH_DATE)).getTime());
props.put(Comment.PROP_PUBLISH_DATE, d);
}
}
// check cq:tags is in the correct format of String[]
if (props.containsKey(CQ_TAGS_PROPERTY)) {
final Object v = props.get(CQ_TAGS_PROPERTY);
if (!(v instanceof String[])) {
if (v instanceof String) {
if (String.valueOf(v).isEmpty()) {
props.remove(CQ_TAGS_PROPERTY);
} else {
props.put(CQ_TAGS_PROPERTY, new String[]{(String) v}); // convert to string array
}
} else {
throw new OperationException("Parameter " + CQ_TAGS_PROPERTY + " is not a String Array",
HttpServletResponse.SC_BAD_REQUEST);
}
}
}
// Filter out controlled scf: prefixed properties
for (final String key : props.keySet()) {
if (StringUtils.startsWith(key, "scf:")) {
props.remove(key);
}
}
// date
if (!props.containsKey(com.adobe.cq.social.commons.Comment.PROP_DATE)
|| (!(props.get(com.adobe.cq.social.commons.Comment.PROP_DATE) instanceof Calendar) && !(props
.get(com.adobe.cq.social.commons.Comment.PROP_DATE) instanceof Date))) {
final Calendar cal = Calendar.getInstance();
props.put(com.adobe.cq.social.commons.Comment.PROP_DATE, cal);
}
message = encodeMessageIfNeeded(message, props, cs.isRteEnabled());
try {
// create comment
final com.adobe.cq.social.commons.Comment comment =
cs.addComment(message, author, attachments, "", getResourceType(targetCommentSystemResource), props);
if (SocialResourceUtils.isSocialResource(comment.getResource())) {
final ModifiableValueMap vm = comment.getResource().adaptTo(ModifiableValueMap.class);
if (vm != null) {
final String entityUrl = getEntityUrl(comment.getResource());
if (!StringUtils.isEmpty(entityUrl)) {
vm.put(SocialUtils.PN_ENTITY, entityUrl);
}
// For post-moderated, it is approved, non SRP will set the property via workflow
if (!cs.isModerated()) {
vm.put(Comment.PROP_APPROVED, true);
}
// add event topic
vm.put(Comment.PROP_EVENT_TOPIC, getEventTopic());
// add publishDate for regular published post
if (vm.get(Comment.PROP_PUBLISH_DATE, Calendar.class) == null
&& !vm.get(Comment.PROP_IS_DRAFT, false)) {
vm.put(Comment.PROP_PUBLISH_DATE, Calendar.getInstance());
}
}
}
String userId = author;
if (props.containsKey(PN_COMPOSED_FOR)) {
userId = comment.getProperty(PN_COMPOSED_FOR, author);
}
boolean throwEvent = true;
if (comment.getProperty(Comment.PROP_IS_DRAFT, false)) {
throwEvent = false;
if (comment.getProperty(Comment.PROP_PUBLISH_DATE, Calendar.class) != null) {
final Calendar publishDate = comment.getProperty(Comment.PROP_PUBLISH_DATE, Calendar.class);
// if the scheduled publish time is in past or now, just continue as regular post
// and update the PN_DATE to the scheduled time;
// if the scheduled time is in future, let SearchUnscheduledPost handles it.
if (publishDate.compareTo(Calendar.getInstance()) <= 0) {
final ModifiableValueMap vm = comment.getResource().adaptTo(ModifiableValueMap.class);
vm.put(Comment.PROP_IS_DRAFT, false);
vm.put(NameConstants.PN_PAGE_LAST_MOD, publishDate);
vm.put(SocialUtils.PN_DATE, publishDate.getTime());
throwEvent = true;
}
}
} else if (cs.isModerated()) {
throwEvent = false;
}
cs.save();
LOG.info("Comment created: " + comment.getPath());
final S commentComp = getSocialComponentForResource(comment.getResource());
performAfterActions(createOperation, session, commentComp, props);
if (throwEvent) {
postCreateEvent(commentComp, userId);
}
return comment.getResource();
} catch (final CommentException e) {
cleanupFailure(session);
throw new OperationException("Failed to create comment.", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private String encodeMessageIfNeeded(final String msg, final Map props, final boolean isRTEEnabled) {
String message = msg;
// Msg is encoded if RTE if off
// Msg is encoded if messageEncoded param is explicitly set and set to false
boolean msgNeedsEncoding = !isRTEEnabled;
if (props.containsKey(PARAM_IS_MESSAGE_ENCODED)) {
final boolean isMsgEncoded = (Boolean) props.get(PARAM_IS_MESSAGE_ENCODED);
if (isMsgEncoded) {
msgNeedsEncoding = false;
} else {
msgNeedsEncoding = true;
}
props.remove(PARAM_IS_MESSAGE_ENCODED);
}
if (msgNeedsEncoding) {
message = StringEscapeUtils.escapeHtml4(message);
}
return message;
}
/**
* @param session
* @throws OperationException
*/
private void cleanupFailure(final Session session) {
try {
session.refresh(false);
} catch (final RepositoryException e) {
LOG.info("Failed to refresh the session", e);
}
}
public Resource create(final Resource root, final String author, final Map props,
final List attachments, final Session session) throws OperationException {
final CommentSystem cs = getCommentSystem(root, session);
final List filteredAttachments = filterAttachments(attachments, cs);
return create(root, cs, author, props, filteredAttachments, session);
}
public Resource create(final SlingHttpServletRequest request, final Session session) throws OperationException {
final Resource resource = request.getResource();
final CommentSystem cs = getCommentSystem(resource, session);
validateParameters(request);
final String name = getAuthorizableId(request, session);
if (name.equals(ANONYMOUS)) {
throw new OperationException("Session is invalid",
HttpServletResponse.SC_UNAUTHORIZED);
}
if (ugcLimiterService == null) {
// this throws IllegalStateException if reference can't be found for the service
ugcLimiterService = Activator.getService(UGCLimiterService.class);
}
if (!ugcLimiterService.mayCreateUGC(request.getResourceResolver(), name)) {
// Dont change the exception message. This exception is used in UI and it may stop working
// Refer to CQ-97142
throw new OperationException("Exceeded contribution limit of " + ugcLimiterService.getUgcLimit()
+ " pieces of content per " + getDurationString(ugcLimiterService.getUgcDuration()) + ". " + getUGCLimitModerators(ugcLimiterService.getUgcLimitedToList()),
HttpServletResponse.SC_FORBIDDEN);
}
if (!mayPost(request, cs, name)) {
throw new OperationException("User not allowed to post to forum at " + cs.getPath(),
HttpServletResponse.SC_PRECONDITION_FAILED);
}
final Map props = new HashMap();
try {
getDefaultProperties(request, props, session);
/*
* if (cs.getProperty(com.adobe.cq.social.commons.comments.api.Comment.PROP_USE_HREF_URL, Boolean.FALSE))
* { if (props.get("Referer") == null && request.getHeader("Referer") != null) { final ResourceResolver
* resolver = resource.getResourceResolver(); final Externalizer externalizer =
* resolver.adaptTo(Externalizer.class); try { URL url = new URL(request.getHeader("Referer")); // this
* part is a bit tricky, do we want to externalize this link? final String referer =
* externalizer.relativeLink(request, url.getPath()); props.put("Referer", referer); } catch
* (MalformedURLException e) { // This should never happen LOG.error("Error parsing referer url", e); } }
* }
*/
getCustomProperties(request, props, session);
} catch (final CommentException e) {
throw new OperationException("Failed to create comment", e,
HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
} catch (final RepositoryException e) {
throw new OperationException("Failed to get comment properties", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
String isComposedForUserExist;
boolean isUserPrivileged = CommentUtil.isUserPrivileged(cs.getResource(), request.getResourceResolver(), name);
if (!isUserPrivileged){
try {
isComposedForUserExist = getStringProperty("composedFor", props);
if (isComposedForUserExist != null){
throw new OperationException("Failed to create comment on behalf of other user",
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} catch (RepositoryException e) {
throw new OperationException("Failed to get the new composedFor property value", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
// create comment
final List attachments = getAttachmentsFromRequest(request, cs);
return create(resource, cs, name, props, attachments, session);
}
public Resource uploadImage(final SlingHttpServletRequest request, final Session session)
throws OperationException {
final Resource resource = request.getResource();
final String name = getAuthorizableId(request, session);
if (name.equals(ANONYMOUS)) {
throw new OperationException("Session is invalid",
HttpServletResponse.SC_UNAUTHORIZED);
}
final CommentSystem cs = getCommentSystem(resource, session);
if (!mayPost(request, cs, name)) {
throw new OperationException("User not allowed to post to comment at " + cs.getPath(),
HttpServletResponse.SC_PRECONDITION_FAILED);
}
final List attachments = getAttachmentsFromRequest(request, cs);
final Map props = new HashMap();
try {
getDefaultProperties(request, props, session);
getCustomProperties(request, props, session);
} catch (final CommentException e) {
LOG.error("Failed to get request properties at " + resource.getPath(), e);
throw new OperationException("Failed to get request properties", e,
HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
} catch (final RepositoryException e) {
LOG.error("Failed to get request properties @ " + resource.getPath(), e);
throw new OperationException("Failed to get comment properties", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return uploadImageToTemp(resource, cs, name, attachments, session);
}
public Resource uploadImageToTemp(final Resource targetCommentSystemResource, final CommentSystem cs,
final String author, final List attachments, final Session session) throws OperationException {
if (cs == null) {
throw new OperationException("Failed to get comment system for target '"
+ targetCommentSystemResource.getPath() + "' ", HttpServletResponse.SC_NOT_FOUND);
}
final List filteredAttachments = filterAttachments(attachments, cs);
// If this is a reply, and the parent comment is closed, throw an exception
final Comment parent = targetCommentSystemResource.adaptTo(Comment.class);
if (parent != null && parent.isClosed()) {
throw new OperationException("Reply attempted on closed comment: "
+ targetCommentSystemResource.getPath(), HttpServletResponse.SC_BAD_REQUEST);
}
if (attachments.size() == 1) { // only support 1 attachment for now
final DataSource data = attachments.get(0);
try {
final Node uploadRoot =
UploadOperationUtils.createOrGetUploadResource(session, targetCommentSystemResource.getPath());
if (attachments.size() == 1) { // only support 1 attachment for now
final String name = Text.getName(data.getName()); // IE sends us the full path.
final Node attachment =
UploadOperationUtils.addAttachment(uploadRoot, name, data.getInputStream(),
data.getContentType());
session.save();
return targetCommentSystemResource.getResourceResolver().getResource(attachment.getPath());
}
} catch (final RepositoryException e) {
throw new OperationException("Could not upload image", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (final IOException e) {
throw new OperationException("Could not upload image", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
throw new OperationException("Invalid number of attachment", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return null;
}
public Resource uploadImage(final Resource targetCommentSystemResource, final CommentSystem cs,
final String author, final Map props, final List attachments,
final Session session) throws OperationException {
if (cs == null) {
LOG.error("Failed to get comment system for target " + targetCommentSystemResource.getPath());
throw new OperationException("Failed to get comment system for target '"
+ targetCommentSystemResource.getPath() + "' ", HttpServletResponse.SC_NOT_FOUND);
}
final U uploadImageOperation = getUploadImageOperation();
if (uploadImageOperation != null) {
final Comment parent = targetCommentSystemResource.adaptTo(Comment.class);
performBeforeActions(uploadImageOperation, session, parent.getResource(), props);
final List filteredAttachments = filterAttachments(attachments, cs);
// If this is a reply, and the parent comment is closed, throw an exception
if (parent != null && parent.isClosed()) {
LOG.error("Reply attempted on closed comment: " + targetCommentSystemResource.getPath());
throw new OperationException("Reply attempted on closed comment: "
+ targetCommentSystemResource.getPath(), HttpServletResponse.SC_BAD_REQUEST);
}
if (parent != null && !mayReply(targetCommentSystemResource, cs)) {
LOG.error("Reply is not allowed: " + targetCommentSystemResource.getPath());
throw new OperationException("Reply is not allowed: " + targetCommentSystemResource.getPath(),
HttpServletResponse.SC_FORBIDDEN);
}
if (attachments.size() == 1) { // only support 1 attachment for now
final DataSource data = attachments.get(0);
try {
final Resource image =
parent.addImage(data.getName(), data.getInputStream(), data.getContentType());
performAfterActions(uploadImageOperation, session,
getSocialComponentForResource(targetCommentSystemResource), props);
image.getResourceResolver().commit();
return image;
} catch (final IOException e) {
LOG.error("IOException while trying to add image at " + targetCommentSystemResource.getPath());
throw new OperationException("IOException while trying to add image", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
LOG.error("Only one attachment supported " + targetCommentSystemResource.getPath());
throw new OperationException("Invalid number of attachment",
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
throw new OperationException("Unsupported operation", HttpServletResponse.SC_BAD_REQUEST);
}
}
private String getStringProperty(final String key, final Map props) throws RepositoryException {
final Object obj = props.get(key);
if (obj == null) {
return null;
}
if (obj instanceof Value) {
return ((Value) obj).getString();
} else {
return obj.toString();
}
}
protected void validateParameters(final SlingHttpServletRequest request) throws OperationException {
final String message = request.getParameter(PROP_MESSAGE);
if (message == null || "".equals(message)) {
throw new OperationException("Comment value is empty", HttpServletResponse.SC_BAD_REQUEST);
}
if (isBot(request)) {
throw new OperationException("Bot check failed: Parameter "
+ com.adobe.cq.social.commons.Comment.PARAM_BOTCHECK + " is missing or has unexpected value",
HttpServletResponse.SC_PRECONDITION_FAILED);
}
}
/**
* Indicates whether the user within the given resource resolver has enough permissions to post to the given
* forum. If no posts to this comment system have yet been made, the permissions on the
* {@link com.adobe.cq.social.commons.CommentSystem#PATH_UGC} path are checked instead.
* @param request The {@link org.apache.sling.api.SlingHttpServletRequest} for which permissions are being
* validated.
* @param cs The {@link com.adobe.cq.social.commons.CommentSystem} the comment system to check permissions for.
* @param userId The user for who is trying post.
* @return true if the user may post.
*/
protected boolean mayPost(final SlingHttpServletRequest request, final CommentSystem cs, final String userId) {
final ResourceResolver resolver = request.getResourceResolver();
if (null != resolver && null != cs) {
if (userId.equals(ANONYMOUS)) {
return false;
}
final ResourceResolver ugcWriterResovler = cs.getResource().getResourceResolver();
Resource r = ugcWriterResovler.getResource(cs.getPath());
final SocialUtils socialUtils = ugcWriterResovler.adaptTo(SocialUtils.class);
if (null != r && null != socialUtils) {
r = ugcWriterResovler.getResource(socialUtils.resourceToACLPath(r));
}
if (null == r) {
// There is a comment system in the content but nothing is posted yet so check the page the forum
// resides
// on to determine if a user can post.
// resourceToUGCPath adds back a lot of extra stuff like jcr:content we can't have so we need to
// construct the path by hand.
r = resolver.getResource(CommentSystem.PATH_UGC);
}
if (r != null) {
return CollabUtil.canAddNode(resolver.adaptTo(Session.class), r.getPath());
}
}
return false;
}
protected boolean mayEdit(final SlingHttpServletRequest request, final CommentSystem cs, final String userId)
throws OperationException {
final boolean mayEdit =
(CollabUtil.hasModeratePermissions(request.getResource()) || CollabUtil.isResourceOwner(request
.getResource()));
if (mayEdit) {
return true;
}
final ValueMap props = request.getResource().adaptTo(ValueMap.class);
final String composedBy = props.get(Comment.PROP_COMPOSED_BY, "");
return mayEdit || StringUtils.equals(userId, composedBy);
}
/**
* Check if the specified root is configured for replying to a comment
* @param root the comment root
* @param cs the CommentSystem
* @return boolean
* @throws OperationException OperationException
*/
protected boolean mayReply(final Resource root, final CommentSystem cs) throws OperationException {
return cs.allowsReplies();
}
protected boolean mayDelete(final SlingHttpServletRequest request, final CommentSystem cs, final String userId)
throws OperationException {
return this.mayEdit(request, cs, userId);
}
protected String getAuthorFromRequest(final SlingHttpServletRequest request) {
String name = request.getParameter(com.adobe.cq.social.ugcbase.CollabUser.PROP_NAME);
if (StringUtils.isBlank(name)) {
final UserProperties up = request.getResourceResolver().adaptTo(UserProperties.class);
name = (up == null) ? null : up.getAuthorizableID();
if (name == null) {
name = com.adobe.cq.social.ugcbase.CollabUser.ANONYMOUS;
}
}
return name;
}
/**
* {@inheritDoc}
*/
public Resource update(final SlingHttpServletRequest request, final Session session) throws OperationException {
final Resource resource = request.getResource();
final CommentSystem cs = getCommentSystem(resource, session);
validateParameters(request);
final String userId = getAuthorizableId(request, session);
if (userId.equals(ANONYMOUS)) {
throw new OperationException("Session is invalid",
HttpServletResponse.SC_UNAUTHORIZED);
}
if (!mayEdit(request, cs, userId)) {
throw new OperationException("User not allowed to edit UGC at " + cs.getPath(),
HttpServletResponse.SC_PRECONDITION_FAILED);
}
final Map props = new HashMap();
try {
getDefaultProperties(request, props, session);
getCustomProperties(request, props, session);
} catch (final CommentException e) {
throw new OperationException("Failed to update UGC", e,
HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
} catch (final RepositoryException e) {
throw new OperationException("Failed to get comment properties", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
String isComposedForUserExist;
boolean isUserPrivileged = CommentUtil.isUserPrivileged(cs.getResource(), request.getResourceResolver(), userId);
if (!isUserPrivileged){
try {
isComposedForUserExist = getStringProperty("composedFor", props);
if (isComposedForUserExist != null){
throw new OperationException("Failed to create comment on behalf of other user",
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} catch (RepositoryException e) {
throw new OperationException("Failed to get the new composedFor property value", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
final List attachments = getAttachmentsFromRequest(request, cs);
RequestParameter[] requestParameters = request.getRequestParameters("attachmentsTobeDeleted");
if (ArrayUtils.isNotEmpty(requestParameters)) {
for (RequestParameter requestParameter : requestParameters) {
String attachmentPath = requestParameter.getString();
removeAttachment(request, session, attachmentPath);
}
}
return update(resource, cs, props, attachments, session, userId);
}
public void removeAttachment(final SlingHttpServletRequest request, final Session session)
throws OperationException {
final String attachmentPath = request.getParameter(PROP_ATT_TO_REMOVE);
removeAttachment(request, session, attachmentPath);
}
private void removeAttachment(final SlingHttpServletRequest request, final Session session,
final String attachmentPath) throws OperationException {
if (StringUtils.isEmpty(attachmentPath)) {
throw new OperationException("Attachment path is empty", HttpServletResponse.SC_BAD_REQUEST);
}
final Resource resource = request.getResource();
final String userId = getAuthorizableId(request, session);
if (userId.equals(ANONYMOUS)) {
throw new OperationException("Session is invalid",
HttpServletResponse.SC_UNAUTHORIZED);
}
final CommentSystem cs = getCommentSystem(resource, session);
if (!mayEdit(request, cs, userId)) {
throw new OperationException("User not allowed to edit UGC at " + cs.getPath(),
HttpServletResponse.SC_PRECONDITION_FAILED);
}
removeAttachment(resource, attachmentPath, session);
}
public void removeAttachment(final Resource commentResource, final String attachment, final Session session)
throws OperationException {
final Comment comment = getComment(commentResource, session);
if (comment == null) {
throw new OperationException("Unable to remove attachment - " + attachment + "for comment - "
+ comment.getPath(), HttpServletResponse.SC_BAD_REQUEST);
}
if (comment.getAttachmentMap() == null || !comment.getAttachmentMap().containsKey(attachment)) {
throw new OperationException("Unable to remove attachment - " + attachment + "for comment - "
+ comment.getPath(), HttpServletResponse.SC_BAD_REQUEST);
}
try {
comment.removeAttachment(attachment);
comment.getResource().getResourceResolver().commit();
} catch (final CommentException e) {
throw new OperationException("Unable to remove attachment for comment " + comment.getPath(), e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (final PersistenceException e) {
throw new OperationException("Unable to remove attachment for comment " + comment.getPath(), e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
protected Resource update(final Resource commentResource, final CommentSystem cs,
final Map props, final List attachments, final Session session,
final String author) throws OperationException {
final com.adobe.cq.social.commons.Comment comment = getComment(commentResource, session);
if (comment == null) {
throw new OperationException("Failed to get Commment for target " + commentResource.getPath(),
HttpServletResponse.SC_NOT_FOUND);
}
if (comment.isClosed() && !CollabUtil.hasModeratePermissions(commentResource)) {
throw new OperationException("Update attempted on closed comment: " + commentResource.getPath(),
HttpServletResponse.SC_BAD_REQUEST);
}
final U updateOperation = getUpdateOperation();
performBeforeActions(updateOperation, session, comment.getResource(), props);
if (cs == null) {
throw new OperationException("Failed to get comment system for target '"
+ comment.getResource().getPath() + "' ", HttpServletResponse.SC_NOT_FOUND);
}
try {
// TODO move this to Comment and don't use Node API
final ModifiableValueMap properties = comment.getResource().adaptTo(ModifiableValueMap.class);
// original publish date if any
final Calendar oldPublishDate = properties.get(Comment.PROP_PUBLISH_DATE, Calendar.class);
final boolean usedToBeDraft = properties.get(Comment.PROP_IS_DRAFT, false);
// Rename the tags property to cq:tags
if (props.containsKey(TAGS_PROPERTY)) {
props.put(CQ_TAGS_PROPERTY, props.get(TAGS_PROPERTY));
props.remove(TAGS_PROPERTY);
}
// remove any existent tags to allow property overwrite
properties.remove(CQ_TAGS_PROPERTY);
if (props.containsKey(PROP_MESSAGE)) {
final long messageCharacterLimit = cs.getMessageCharacterLimit();
String message = CollabUtil.getValueString(props.get(PROP_MESSAGE));
if (message == null) {
throw new OperationException("Null value for comment message.",
HttpServletResponse.SC_BAD_REQUEST);
}
final String normalizedMessage = Normalizer.normalize(message, Normalizer.Form.NFC);
if (normalizedMessage.codePointCount(0, normalizedMessage.length()) > messageCharacterLimit) {
throw new OperationException("Parameter " + PROP_MESSAGE + " exceeded character limit",
HttpServletResponse.SC_BAD_REQUEST);
}
message = encodeMessageIfNeeded(message, props, cs.isRteEnabled());
properties.put(com.adobe.cq.social.commons.Comment.PROP_MESSAGE, message);
}
for (final Entry entry : props.entrySet()) {
if (!StringUtils.equals(entry.getKey(), PROP_MESSAGE)) {
properties.put(entry.getKey(), entry.getValue());
}
}
// trigger moderation if it is pre-moderated
if (cs.isModerated()) {
properties.remove(com.adobe.cq.social.commons.Comment.PROP_APPROVED);
}
properties.put(com.adobe.cq.social.commons.Comment.PROP_MODERATE, Boolean.TRUE);
properties.put(NameConstants.PN_PAGE_LAST_MOD, Calendar.getInstance());
if (CollabUtil.hasModeratePermissions(comment.getResource())) {
properties.put(NameConstants.PN_PAGE_LAST_MOD_BY, session.getUserID());
} else {
properties.put(NameConstants.PN_PAGE_LAST_MOD_BY, comment.getAuthor().getId());
}
updateAttachments(comment, attachments);
/**
* users can save comment as publish immediately, draft, and publish later at any time. this is expected
* behavior when a comment is switched between the three modes. immediately -> immediately [throw Update
* event] immediately -> draft [--] immediately -> later [*] draft -> immediately [throw Create event]
* draft -> draft [--] draft -> later [*] later -> immediately [throw Create event][**] later -> draft
* [--][**] later -> later [*][***] [*] if scheduled publish time is passed, treat it as publish
* immediately. [**] cancel the job has been scheduled for the comment, if any. [***] if the publish time
* is changed, cancel old job.
*/
boolean isCurrentDraft = false;
if (props.containsKey(Comment.PROP_IS_DRAFT)) {
isCurrentDraft = (Boolean) props.get(Comment.PROP_IS_DRAFT);
}
Calendar currentPublishDate = null;
if (props.containsKey(Comment.PROP_PUBLISH_DATE)) {
currentPublishDate = (Calendar) props.get(Comment.PROP_PUBLISH_DATE);
}
// cancel job when publish-later is changed
boolean cancelJob = false;
boolean forcePublish = false;
if (!isCurrentDraft && usedToBeDraft && oldPublishDate != null) {
// publish-later -> publish immediately
cancelJob = true;
} else if (isCurrentDraft && oldPublishDate != null
&& (currentPublishDate == null || oldPublishDate.compareTo(currentPublishDate) != 0)) {
// 1. publish-later date is updated
// 2. publish-later -> draft
cancelJob = true;
}
if (isCurrentDraft && currentPublishDate != null
&& currentPublishDate.compareTo(Calendar.getInstance()) <= 0) {
// if the scheduled publish time is in past or now, so publish it now
properties.put(Comment.PROP_IS_DRAFT, Boolean.FALSE);
properties.put(NameConstants.PN_PAGE_LAST_MOD, currentPublishDate);
properties.put(SocialUtils.PN_DATE, currentPublishDate.getTime());
cancelJob = true;
forcePublish = true;
}
if (cancelJob) {
// cancel job if scheduled
final String oldJob = properties.get(Comment.PROP_PUBLISH_JOB_ID, String.class);
if (currentPublishDate == null) {
properties.remove(Comment.PROP_PUBLISH_DATE);
}
if (StringUtils.isNotEmpty(oldJob)) {
futurePostScheduler.unschedule(oldJob);
properties.remove(Comment.PROP_PUBLISH_JOB_ID);
}
}
if (!isCurrentDraft && usedToBeDraft) {
// 1. draft -> publish immediately
// 2. publish-later -> publish immediately
properties.put(Comment.PROP_PUBLISH_DATE, Calendar.getInstance());
}
comment.getResource().getResourceResolver().commit();
final com.adobe.cq.social.commons.Comment updatedComment =
comment.getResource().adaptTo(com.adobe.cq.social.commons.Comment.class);
final S commentComp = getSocialComponentForResource(updatedComment.getResource());
if (!isCurrentDraft) {
if (!usedToBeDraft) {
// regular post update
postUpdateEvent(commentComp, author);
} else {
// 1. draft -> publish immediately
// 2. publish-later -> publish immediately
postCreateEvent(commentComp, author);
}
} else if (forcePublish) {
postCreateEvent(commentComp, author);
}
performAfterActions(updateOperation, session, commentComp, props);
return updatedComment.getResource();
} catch (final RepositoryException e) {
throw new OperationException("Failed to update comment", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (final PersistenceException e) {
throw new OperationException("Failed to update comment", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (final IllegalArgumentException e) {
throw new OperationException("Failed to update comment", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
protected void updateAttachments(final Comment comment, final List attachments) {
if (attachments != null) {
comment.addAttachments(attachments);
}
}
public Resource update(final Resource commentResource, final Map props,
final List attachments, final Session session) throws OperationException {
final CommentSystem cs = getCommentSystem(commentResource, session);
return update(commentResource, cs, props, attachments, session, commentResource.getResourceResolver()
.getUserID());
}
public void delete(final SlingHttpServletRequest request, final Session session) throws OperationException {
delete(request, session, Collections. emptyMap());
}
/**
* Delete a {@link Comment} specified by the {@link SlingHttpServletRequest} using the specified {@link Session}
* @param request SlingHttpServletRequest
* @param session Session
* @param conditionals params deciding execution flow
* @throws OperationException OperationException
*/
public void delete(final SlingHttpServletRequest request, final Session session, final Map conditionals) throws OperationException {
final Resource reqResource = request.getResource();
final CommentSystem cs = getCommentSystem(reqResource, session);
final com.adobe.cq.social.commons.Comment comment = getComment(reqResource, session);
final boolean isDraft = comment.getProperty(Comment.PROP_IS_DRAFT, false);
final String userId = getAuthorizableId(request, session);
if (userId.equals(ANONYMOUS)) {
throw new OperationException("Session is invalid",
HttpServletResponse.SC_UNAUTHORIZED);
}
// if draft - delete should be allowed even if delete operation is not enabled
if (!cs.allowsDelete() && !isDraft) {
throw new OperationException("Deletion is not enabling.", HttpServletResponse.SC_FORBIDDEN);
}
if (!mayDelete(request, cs, userId)) {
throw new OperationException("User not allowed to delete " + cs.getPath(),
HttpServletResponse.SC_NOT_FOUND);
}
if (conditionals.isEmpty()) {
delete(reqResource, cs, session);
} else {
delete(reqResource, cs, session, conditionals);
}
}
public void delete(final Resource commentResource, final Session session) throws OperationException {
delete(commentResource, session, Collections. emptyMap());
}
public void delete(final Resource commentResource, final Session session, final Map conditionals) throws OperationException {
final CommentSystem cs = getCommentSystem(commentResource, session);
delete(commentResource, cs, session, conditionals);
}
public Resource move(final SlingHttpServletRequest request, final Session session) throws OperationException {
final String resourcePath = request.getParameter("resourcePath");
final String parentPath = request.getParameter("parentPath");
final ResourceResolver resolver = request.getResourceResolver();
if (null == resolver) {
throw new OperationException("Request does not provide a resource resolver",
HttpServletResponse.SC_FORBIDDEN);
}
final Resource resource = resolver.getResource(resourcePath);
if (null == resource) {
throw new OperationException("The resource to be moved could not be found at " + resourcePath,
HttpServletResponse.SC_NOT_FOUND);
}
final Resource parent = resolver.getResource(parentPath);
if (null == parent) {
throw new OperationException("The target resource could not be found at " + parentPath,
HttpServletResponse.SC_NOT_FOUND);
}
return move(resource, parent, request.getResourceResolver().adaptTo(Session.class));
}
public Resource move(final Resource commentResource, final Resource parentResource, final Session session)
throws OperationException {
final ResourceResolver resolver = commentResource.getResourceResolver();
final String resourcePath = commentResource.getPath();
final String parentPath = parentResource.getPath();
final SocialUtils socialUtils = resolver.adaptTo(SocialUtils.class);
final String testPath = resourcePath.substring(0, resourcePath.lastIndexOf("/"));
final Boolean needMove =
SocialResourceUtils.isCloudUGC(resourcePath) ? !socialUtils.resourceToUGCStoragePath(parentResource)
.equals(testPath) : !parentPath.equals(testPath);
if (needMove) {
try {
if (!socialUtils.hasModeratePermissions(commentResource)
|| !socialUtils.hasModeratePermissions(parentResource)) {
throw new OperationException(
"The user does not have sufficient privileges to perform the move operation",
HttpServletResponse.SC_FORBIDDEN);
}
if (!SocialResourceUtils.isCloudUGC(parentPath)) {
session.checkPermission(socialUtils.resourceToUGCStoragePath(parentResource),
Session.ACTION_ADD_NODE);
} else {
session.checkPermission(parentPath, Session.ACTION_ADD_NODE);
}
} catch (final AccessControlException e) {
throw new OperationException(
"The user does not have sufficient privileges to perform the move operation", e,
HttpServletResponse.SC_FORBIDDEN);
} catch (final RepositoryException e) {
throw new OperationException("Unable to complete a permission check", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
final SocialResourceProvider srp = socialUtils.getConfiguredProvider(commentResource);
try {
final Resource newResource = srp.move(resolver, resourcePath, parentPath);
if (null == newResource) {
srp.revert(resolver);
throw new OperationException("Unable to move " + resourcePath + " to " + parentPath,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
// perform before actions for creating the new resource
final U createOperation = getCreateOperation();
final Map props = newResource.adaptTo(ModifiableValueMap.class);
final S commentComp = getSocialComponentForResource(newResource);
// Check if pinning allowed
final Boolean allowPinning = commentComp.getConfiguration().isPinAllowed();
final Boolean isPinned =
(Boolean) (props.get(com.adobe.cq.social.commons.Comment.PROP_PINNED) == null ? false : props
.get(com.adobe.cq.social.commons.Comment.PROP_PINNED));
if (!allowPinning && isPinned) {
props.remove(com.adobe.cq.social.commons.Comment.PROP_PINNED);
}
performBeforeActions(createOperation, session,
resolver.getResource((String) props.get(SocialUtils.PN_CS_ROOT)), props);
srp.commit(resolver);
// perform after actions for create
performAfterActions(createOperation, session, commentComp, props);
postCreateEvent(commentComp, session.getUserID());
return newResource;
} catch (final PersistenceException e) {
srp.revert(resolver);
throw new OperationException("Unable to move " + resourcePath + " to " + parentPath, e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
return commentResource;
}
}
public Resource changeState(final Resource commentResource, final String author, final Map props,
final List attachments, final Session session) throws OperationException {
final CommentSystem cs = getCommentSystem(commentResource, session);
return changeState(commentResource, cs, author, props, session);
}
public Resource changeState(final SlingHttpServletRequest request, final Session session)
throws OperationException {
final Resource resource = request.getResource();
final String name = getAuthorizableId(request, session);
final CommentSystem cs = getCommentSystem(resource, session);
final com.adobe.cq.social.commons.Comment comment = getComment(resource, session);
// get properties
final Map props = new HashMap();
try {
getDefaultProperties(request, props, session);
// the state operation
final String operation = request.getParameter(PROPERTY_STATE_OPERATION);
if (StringUtils.isNotBlank(operation)) {
props.put(PROPERTY_STATE_OPERATION, operation);
}
// toState
final String toState = request.getParameter(PROPERTY_TOSTATE);
if (StringUtils.isNotBlank(toState)) {
props.put(PROPERTY_TOSTATE, toState);
}
// is addReply
final String isAddReply = request.getParameter(PROPERTY_ADD_REPLY);
if (StringUtils.isNotBlank(isAddReply)) {
props.put(PROPERTY_ADD_REPLY, Boolean.parseBoolean(isAddReply));
}
getCustomProperties(request, props, session);
} catch (final CommentException e) {
throw new OperationException("Failed to change comment state", e,
HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
} catch (final RepositoryException e) {
throw new OperationException("Failed to get comment properties", e,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return changeState(resource, cs, name, props, session);
}
protected Resource changeState(final Resource commentResource, final CommentSystem cs, final String author,
final Map props, final Session session) throws OperationException {
final com.adobe.cq.social.commons.Comment comment = getComment(commentResource, session);
if (comment == null) {
throw new OperationException("Failed to get Commment for target " + commentResource.getPath(),
HttpServletResponse.SC_NOT_FOUND);
}
if (comment.isClosed() && !CollabUtil.hasModeratePermissions(commentResource)) {
throw new OperationException("Update attempted on closed comment: " + commentResource.getPath(),
HttpServletResponse.SC_BAD_REQUEST);
}
final U changeStateOperation = getChangeStateOperation();
performBeforeActions(changeStateOperation, session, comment.getResource(), props);
if (cs == null) {
throw new OperationException("Failed to get comment system for target '"
+ comment.getResource().getPath() + "' ", HttpServletResponse.SC_NOT_FOUND);
}
if (!props.containsKey(PROPERTY_STATE_OPERATION)) {
throw new OperationException("Cannot find state operation from request",
HttpServletResponse.SC_BAD_REQUEST);
}
final String operation = (String) props.get(PROPERTY_STATE_OPERATION);
if (!props.containsKey(PROPERTY_TOSTATE)) {
throw new OperationException("Cannot find tostate from request", HttpServletResponse.SC_BAD_REQUEST);
}
final String toState = (String) props.get(PROPERTY_TOSTATE);
// TODO: validate operation - should be in state machine
final StateMachine stateMachine = commentResource.adaptTo(StateMachine.class);
final S commentComp = getSocialComponentForResource(comment.getResource());
final State state = stateMachine.getState(commentComp.getState());
String role = null;
boolean validOp = false;
if (state != null) {
if (CollabUtil.hasModeratePermissions(commentResource)) {
validOp = state.canPerformOperation(MODERATORS_ROLE, operation);
role = MODERATORS_ROLE;
}
if (!validOp && CollabUtil.isResourceOwner(commentResource)) {
validOp = state.canPerformOperation(OWNER_ROLE, operation);
role = OWNER_ROLE;
}
} else {
throw new OperationException("Failed to get state for target " + commentResource.getPath(),
HttpServletResponse.SC_NOT_FOUND);
}
if (!validOp) {
throw new OperationException("Failed to change comment state",
HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION);
}
// change state of the comment
state
.performOperation(commentResource, comment.getResource().getResourceResolver(), role, operation, toState);
// if a message is included, create a reply
// this only works for review idea operation
// save operation registration for later
Resource replyRes = null;
if (props.containsKey(PROPERTY_ADD_REPLY) && ((Boolean) props.get(PROPERTY_ADD_REPLY)).booleanValue()) {
replyRes = create(commentResource, cs, author, props, Collections.emptyList(), session);
}
postChangeStateEvent(commentComp, author);
performAfterActions(changeStateOperation, session, commentComp, props);
return replyRes == null ? commentResource : replyRes;
}
public void delete(final Resource commentResource, final CommentSystem cs, final Session session)
throws OperationException {
delete(commentResource, cs, session, Collections. emptyMap());
}
/**
* Process a comment delete request sent by a client. Sends back a 204 if the delete succeeded with no entity.
* @param commentResource the comment to delete
* @param cs the comment system
* @param session the session used to delete
* @param conditionals Map
* @throws OperationException when the session cannot save the changes in the comment system
*/
public void delete(final Resource commentResource, final CommentSystem cs, final Session session, final Map conditionals)
throws OperationException {
LOG.debug("delete for {} ", commentResource.getPath());
final com.adobe.cq.social.commons.Comment comment = getComment(commentResource, session);
if (comment == null) {
throw new OperationException("Unable to get comment for resource at " + commentResource.getPath(),
HttpServletResponse.SC_NOT_FOUND);
}
if (comment.isClosed() && !CollabUtil.hasModeratePermissions(commentResource)) {
throw new OperationException("Delete attempted on closed comment: " + commentResource.getPath(),
HttpServletResponse.SC_BAD_REQUEST);
}
final S commentComp = getSocialComponentForResource(comment.getResource());
final U deleteOperation = getDeleteOperation();
performBeforeActions(deleteOperation, session, comment.getResource(), Collections.emptyMap());
commentComp.getParentComponent(); // Force this to be read before the data goes away.
final String author = commentResource.getResourceResolver().getUserID();
// handle draft and scheduled posts
boolean throwEvent = true;
final ValueMap properties = comment.getResource().adaptTo(ModifiableValueMap.class);
final Boolean isDraft = properties.get(Comment.PROP_IS_DRAFT, Boolean.class);
if (isDraft != null && isDraft) {
throwEvent = false;
final String oldJob = properties.get(Comment.PROP_PUBLISH_JOB_ID, String.class);
// cancel previous job
if (StringUtils.isNotEmpty(oldJob)) {
futurePostScheduler.unschedule(oldJob);
}
}
final List comments = new ArrayList();
CommentUtil.getComments(comment, comments);
final ResourceResolver resolver = comment.getResource().getResourceResolver();
comment.remove();
comment.getCommentSystem().save();
if (throwEvent && (conditionals.get(IGNORE_EVENT_POST) == null || (Boolean)conditionals.get(IGNORE_EVENT_POST).equals(false))) {
postDeleteEvent(commentComp, author);
}
deleteActivities(comments, resolver);
deleteRelationships(commentResource, resolver);
performAfterActions(deleteOperation, session, commentComp, Collections.emptyMap());
}
private void deleteActivities(final List comments, final ResourceResolver resolver)
throws OperationException {
for (final String commentPath : comments) {
final List activityResources = CommentUtil.getActivitiesForComment(resolver, commentPath);
if (activityResources != null) {
try {
for(Resource activityResource:activityResources) {
resolver.delete(activityResource);
resolver.commit();
}
} catch (final PersistenceException e) {
LOG.warn("Failed to delete activity for comment '{}'", commentPath);
}
}
}
}
private void deleteRelationships(final Resource commentResource, final ResourceResolver resolver) {
String types[] = {SUBSCRIPTION, NOTIFICATION, ACTIVITYSTREAMS};
final SocialGraph graph = resolver.adaptTo(SocialGraph.class);
final Vertex subscriptionTargetNode = graph.getVertex(commentResource.getPath());
if (subscriptionTargetNode != null ) {
for (int i = 0; i < types.length; i++) {
final Iterable relationships = subscriptionTargetNode.getRelationships(Direction.INCOMING, types[i]);
if (relationships != null) {
for (Relationship relationship : relationships) {
relationship.delete();
}
}
}
}
try {
resolver.commit();
} catch (PersistenceException ex) {
LOG.warn("Failed to delete subscriptions for comment '{}'", commentResource.getPath());
}
}
/**
* Collects the paths with the deepest nodes coming first so they can be deleted in a bottom up order.
* @param resolver resolver to use to get resource from string path
* @param path starting path
* @return list of paths under the current node
* @throws RepositoryException when the node cannot be found
*/
private List getPaths(final ResourceResolver resolver, final String path) throws RepositoryException {
// Collect all the child nodes of this node, otherwise we might leave orphans around
// don't just restrict to the attachments.
final List paths = new ArrayList();
final Resource resource = resolver.getResource(path);
if (resource == null) {
return paths;
}
for (final Resource child : resource.getChildren()) {
paths.addAll(getPaths(resolver, child.getPath()));
}
paths.add(path);
return paths;
}
private String getUGCLimitModerators(String[] moderatorList) {
StringBuilder strBuilder = new StringBuilder();
if(moderatorList != null && moderatorList.length != 0) {
strBuilder.append(moderatorList[0]);
for (int i = 1; i < moderatorList.length; i++) {
strBuilder.append(", " + moderatorList[i]);
}
return "Please contact :" + strBuilder.toString();
} else {
return "";
}
}
private String getDurationString(final long duration) {
final int second = 1000;
final int minute = second * 60;
final int hour = minute * 60;
final long day = hour * 24;
String durationString = "minute";
if (duration < minute) {
final long t = Math.round(duration / second);
if (t == 1l) {
durationString = "second";
}
durationString = "second";
} else if (duration < hour) {
final long t = Math.round(duration / minute);
if (t == 1l) {
durationString = "minute";
}
durationString = "minute";
} else if (duration < day) {
final long t = Math.round(duration / hour);
if (t == 1l) {
durationString = "hour";
}
durationString = "hour";
} else {
durationString = "day";
}
return durationString;
}
protected String getEntityUrl(final Resource resource) {
return resource.adaptTo(CommentSystem.class).getUrl();
}
protected String getEventTopic() {
return CommentEvent.COMMENT_TOPIC;
}
private static AtomicBoolean userManagerCompatibilityMessageLogged = new AtomicBoolean(false);
/* deprecated as of 6.2 FP2/6.3 */
@Deprecated
protected void bindUserManagerFactory(final Object in) {
// NOP for compatibility with concrete impls still binding obsolete com.day.cq.security.UserManagerFactory
unbindUserManagerFactory(in);
}
/* deprecated as of 6.2 FP2/6.3 */
@Deprecated
protected void unbindUserManagerFactory(final Object in) {
// NOP for compatibility with concrete impls still binding obsolete com.day.cq.security.UserManagerFactory
if (!userManagerCompatibilityMessageLogged.getAndSet(true)) {
LOG.info("Service binding for deprecated and unsupported UserManagerFactory encountered. "
+ "Ensure all direct and indirect uses of AbstractCommentOperationService are recompiled with the latest applicable code.");
}
}
/**
* {@inheritDoc}
*/
protected abstract S getSocialComponentForResource(final Resource resource);
/**
* Posts an OSGi create social event for this component.
* @param component the component
* @param authorId the person who is creating the content
*/
protected abstract void postCreateEvent(S component, String authorId);
/**
* Posts an OSGi update social event for this component.
* @param component the component
* @param authorId the person who is updating the content
*/
protected abstract void postUpdateEvent(S component, String authorId);
/**
* Posts an OSGi change state social event for this component.
* @param component the component
* @param authorId the person who is updating the content
*/
protected abstract void postChangeStateEvent(S component, String authorId);
/**
* Posts an OSGi delete social event for this component.
* @param component the component
* @param authorId the person who is deleting the content
*/
protected abstract void postDeleteEvent(S component, String authorId);
protected abstract U getCreateOperation();
protected abstract U getDeleteOperation();
protected abstract U getUpdateOperation();
protected abstract U getUploadImageOperation();
protected abstract U getChangeStateOperation();
protected abstract String getResourceType(final Resource root);
}