blob: b85ff5aec45e7e6e53039d74523dfc8dc7105f56 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.jackrabbit.usermanager.impl.post;
import java.lang.reflect.Array;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.request.builder.Builders;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.apache.sling.jackrabbit.usermanager.PrincipalNameFilter;
import org.apache.sling.jackrabbit.usermanager.PrincipalNameGenerator;
import org.apache.sling.jackrabbit.usermanager.PrincipalNameGenerator.NameInfo;
import org.apache.sling.jackrabbit.usermanager.resource.SystemUserManagerPaths;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.apache.sling.servlets.post.Modification;
import org.apache.sling.servlets.post.SlingPostConstants;
import org.apache.sling.servlets.post.impl.helper.DateParser;
import org.apache.sling.servlets.post.impl.helper.RequestProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for all the POST servlets for the UserManager operations
*/
public abstract class AbstractAuthorizablePostServlet extends
AbstractPostServlet {
private static final long serialVersionUID = -5918670409789895333L;
private static final class PrincipalNameGeneratorHolder {
private final PrincipalNameGenerator generator;
private final int ranking;
private PrincipalNameGeneratorHolder(PrincipalNameGenerator generator, int ranking) {
this.generator = generator;
this.ranking = ranking;
}
public PrincipalNameGenerator getGenerator() {
return generator;
}
}
protected static final String RP_NODE_NAME_VALUE_FROM = String.format("%s%s", SlingPostConstants.RP_NODE_NAME, SlingPostConstants.VALUE_FROM_SUFFIX);
protected static final String RP_NODE_NAME_HINT_VALUE_FROM = String.format("%s%s", SlingPostConstants.RP_NODE_NAME_HINT, SlingPostConstants.VALUE_FROM_SUFFIX);
public static final String PROP_DATE_FORMAT = "servlet.post.dateFormats";
private static final Logger LOG = LoggerFactory.getLogger(AbstractAuthorizablePostServlet.class);
private final SecureRandom randomCollisionIndex = new SecureRandom();
private transient DateParser dateParser;
protected transient SystemUserManagerPaths systemUserManagerPaths;
protected void bindSystemUserManagerPaths(SystemUserManagerPaths sump) {
this.systemUserManagerPaths = sump;
}
/**
* The principal name generators
*/
protected transient LinkedList<PrincipalNameGeneratorHolder> principalNameGenerators = new LinkedList<>();
/**
* The optional principal name filter
*/
protected transient PrincipalNameFilter principalNameFilter;
/**
* Bind a new principal name generator
*/
// @Reference(service = PrincipalNameGenerator.class)
protected void bindPrincipalNameGenerator(final PrincipalNameGenerator generator, final Map<String, Object> properties) {
final PrincipalNameGeneratorHolder pngh = new PrincipalNameGeneratorHolder(generator, getRanking(properties));
synchronized (principalNameGenerators) {
this.principalNameGenerators.add(pngh);
Collections.sort(this.principalNameGenerators, (o1, o2) ->
Integer.compare(o1.ranking, o2.ranking));
}
}
protected void unbindPrincipalNameGenerator(final PrincipalNameGenerator generator) {
synchronized (principalNameGenerators) {
principalNameGenerators.removeIf(h -> h.generator == generator);
}
}
/**
* Bind a new principal name filter
*/
// @Reference(service = PrincipalNameFilter.class)
protected void bindPrincipalNameFilter(final PrincipalNameFilter filter) {
this.principalNameFilter = filter;
}
protected void unbindPrincipalNameFilter(final PrincipalNameFilter filter) {
if (filter != null && filter.equals(this.principalNameFilter)) {
this.principalNameFilter = null;
}
}
/**
* Get or generate the name of the principal being created
*
* @param request the current request
* @return the principal name
*/
protected String getOrGeneratePrincipalName(Session jcrSession, Map<String, ?> properties, AuthorizableType type) throws RepositoryException {
String principalName = null;
PrincipalNameGenerator defaultPrincipalNameGenerator = null;
PrincipalNameGenerator principalNameGenerator = null;
synchronized (principalNameGenerators) {
if (!principalNameGenerators.isEmpty()) {
defaultPrincipalNameGenerator = principalNameGenerators.getFirst().getGenerator();
principalNameGenerator = principalNameGenerators.getLast().getGenerator();
}
}
if (principalNameGenerator != null) {
NameInfo nameInfo = principalNameGenerator.getPrincipalName(properties, type,
principalNameFilter, defaultPrincipalNameGenerator);
if (nameInfo == null && defaultPrincipalNameGenerator != null) {
// fallback to the default impl
nameInfo = defaultPrincipalNameGenerator.getPrincipalName(properties, type,
principalNameFilter, defaultPrincipalNameGenerator);
}
if (nameInfo != null) {
principalName = nameInfo.getPrincipalName();
if (principalName != null && nameInfo.isMakeUnique()) {
// make sure the name is not already used
UserManager um = AccessControlUtil.getUserManager(jcrSession);
// if resulting authorizable exists, add a random suffix until it's not the case
// anymore
final int MAX_TRIES = 1000;
if (um.getAuthorizable(principalName) != null ) {
for (int i=0; i < MAX_TRIES; i++) {
final int uniqueIndex = randomCollisionIndex.nextInt(9999);
String newPrincipalName = principalName + "_" + uniqueIndex;
if (um.getAuthorizable(newPrincipalName) == null) {
// found unused value, so use it
principalName = newPrincipalName;
break;
}
}
// Give up after MAX_TRIES
if (um.getAuthorizable(principalName) != null ) {
throw new RepositoryException(
"Collision in generated principal names, generated name " + principalName + " already exists");
}
}
}
}
} else {
// fallback to the old behavior
Object obj = properties.get(SlingPostConstants.RP_NODE_NAME);
if (obj instanceof String[] && Array.getLength(obj) == 1) {
principalName = ((String[])obj)[0];
} else if (obj instanceof String) {
principalName= ((String)obj);
}
}
return principalName;
}
// ---------- SCR Integration ----------------------------------------------
protected void activate(Map<String, Object> props) {
dateParser = new DateParser();
String[] dateFormats = OsgiUtil.toStringArray(props.get(PROP_DATE_FORMAT));
for (String dateFormat : dateFormats) {
dateParser.register(dateFormat);
}
}
protected void deactivate() {
dateParser = null;
}
// ------ The methods below are based on the private methods from the
// ModifyOperation class -----
/**
* Collects the properties that form the content to be written back to the
* repository.
* @param properties the properties out of which to generate the {@link RequestProperty}s
* @return the list of {@link RequestProperty}s
* @deprecated use {@link #collectContentMap(Map)} instead since 2.2.18
*/
@Deprecated
protected Collection<RequestProperty> collectContent(
Map<String, ?> properties) {
return collectContentMap(properties).values();
}
/**
* Collects the properties that form the content to be written back to the
* repository.
* @param properties the properties out of which to generate the {@link RequestProperty}s
* @return the list of {@link RequestProperty}s
*/
protected Map<String, RequestProperty> collectContentMap(
Map<String, ?> properties) {
boolean requireItemPrefix = requireItemPathPrefix(properties);
// walk the request parameters and collect the properties (the key is the property path).
Map<String, RequestProperty> reqProperties = new HashMap<>();
for (Map.Entry<String, ?> e : properties.entrySet()) {
final String paramName = e.getKey();
// do not store parameters with names starting with sling:post
boolean skipParam = paramName.startsWith(SlingPostConstants.RP_PREFIX);
// SLING-298: skip form encoding parameter
if (paramName.equals("_charset_")) {
skipParam = true;
}
// skip parameters that do not start with the save prefix
if (requireItemPrefix && !hasItemPathPrefix(paramName)) {
skipParam = true;
}
// ensure the paramName is an absolute property path (i.e. starts with "/", where root refers to the authorizable's root, https://issues.apache.org/jira/browse/SLING-1577)
String propPath;
if (paramName.startsWith("./")) {
propPath = paramName.substring(1);
} else {
propPath = String.format("/%s", paramName);
}
if (propPath.indexOf("..") != -1) {
// it is not supported to set properties potentially outside of the authorizable node
LOG.warn("Property path containing '..' is not supported, skipping parameter {}", paramName);
skipParam = true;
}
if (!skipParam) {
// @TypeHint example
// <input type="text" name="./age" />
// <input type="hidden" name="./age@TypeHint" value="long" />
// causes the setProperty using the 'long' property type
if (propPath.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) {
RequestProperty prop = getOrCreateRequestProperty(
reqProperties, propPath,
SlingPostConstants.TYPE_HINT_SUFFIX);
String typeHintValue = convertToString(e.getValue());
if (typeHintValue != null) {
prop.setTypeHintValue(typeHintValue);
}
} else if (propPath.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) {
// @DefaultValue
RequestProperty prop = getOrCreateRequestProperty(
reqProperties, propPath,
SlingPostConstants.DEFAULT_VALUE_SUFFIX);
prop.setDefaultValues(convertToRequestParameterArray(e.getValue()));
} else if (propPath.endsWith(SlingPostConstants.VALUE_FROM_SUFFIX)) {
// SLING-130: VALUE_FROM_SUFFIX means take the value of this
// property from a different field
// @ValueFrom example:
// <input name="./Text@ValueFrom" type="hidden" value="fulltext" />
// causes the JCR Text property to be set to the value of the
// fulltext form field.
RequestProperty prop = getOrCreateRequestProperty(
reqProperties, propPath,
SlingPostConstants.VALUE_FROM_SUFFIX);
// @ValueFrom params must have exactly one value, else ignored
String [] valueFrom = convertToStringArray(e.getValue());
if (valueFrom.length == 1) {
String refName = valueFrom[0];
prop.setValues(convertToRequestParameterArray(refName));
}
} else if (propPath.endsWith(SlingPostConstants.SUFFIX_DELETE)) {
// SLING-458: Allow Removal of properties prior to update
// @Delete example:
// <input name="./Text@Delete" type="hidden" />
// causes the JCR Text property to be deleted before update
RequestProperty prop = getOrCreateRequestProperty(
reqProperties, propPath, SlingPostConstants.SUFFIX_DELETE);
prop.setDelete(true);
} else if (propPath.endsWith(SlingPostConstants.SUFFIX_MOVE_FROM)) {
// SLING-455: @MoveFrom means moving content to another location
// @MoveFrom example:
// <input name="./Text@MoveFrom" type="hidden" value="/tmp/path" />
// causes the JCR Text property to be set by moving the /tmp/path
// property to Text.
// don't support @MoveFrom here
LOG.warn("Suffix {} not supported, skipping parameter {}", SlingPostConstants.SUFFIX_MOVE_FROM, paramName);
} else if (propPath.endsWith(SlingPostConstants.SUFFIX_COPY_FROM)) {
// SLING-455: @CopyFrom means moving content to another location
// @CopyFrom example:
// <input name="./Text@CopyFrom" type="hidden" value="/tmp/path" />
// causes the JCR Text property to be set by copying the /tmp/path
// property to Text.
// don't support @CopyFrom here
LOG.warn("Suffix {} not supported, skipping parameter {}", SlingPostConstants.SUFFIX_COPY_FROM, paramName);
} else {
// plain property, create from values
RequestProperty prop = getOrCreateRequestProperty(reqProperties,
propPath, null);
prop.setValues(convertToRequestParameterArray(e.getValue()));
}
}
}
return reqProperties;
}
/**
* Returns the request property for the given property path. If such a
* request property does not exist yet it is created and stored in the
* <code>props</code>.
*
* @param props The map of already seen request properties
* (key is the property path).
* @param paramPath The absolute path of the property including the
* <code>suffix</code> to be looked up.
* @param suffix The (optional) suffix to remove from the
* <code>paramName</code> before looking it up.
* @return The {@link RequestProperty} for the <code>paramName</code>.
*/
private RequestProperty getOrCreateRequestProperty(
Map<String, RequestProperty> props, String paramPath, String suffix) {
if (suffix != null && paramPath.endsWith(suffix)) {
paramPath = paramPath.substring(0, paramPath.length()
- suffix.length());
}
return props.computeIfAbsent(paramPath, RequestProperty::new);
}
/**
* Removes all properties listed as {@link RequestProperty#isDelete()} from
* the authorizable.
*
* @param authorizable The
* <code>org.apache.jackrabbit.api.security.user.Authorizable</code>
* that should have properties deleted.
* @param reqProperties The collection of request properties to check for
* properties to be removed.
* @param changes The <code>List</code> to be updated with
* information on deleted properties.
* @throws RepositoryException Is thrown if an error occurrs checking or
* removing properties.
*/
protected void processDeletes(Authorizable authorizable,
Collection<RequestProperty> reqProperties,
List<Modification> changes) throws RepositoryException {
for (RequestProperty property : reqProperties) {
if (property.isDelete()) {
// SLING-7901 - remove artificial "/" prepended to the prop path
String relativePath = property.getPath().substring(1);
if (authorizable.hasProperty(relativePath)) {
authorizable.removeProperty(relativePath);
changes.add(Modification.onDeleted(relativePath));
}
}
}
}
/**
* Create resource(s) according to current request
*
* @param session the sessioin to write the authorizable properties
* @param authorizable The
* <code>org.apache.jackrabbit.api.security.user.Authorizable</code>
* that should have properties deleted.
* @param reqProperties The collection of request properties to check for
* properties to be removed.
* @param changes The <code>List</code> to be updated with
* information on deleted properties.
* @throws RepositoryException Is thrown if an error occurrs checking or
* removing properties.
*/
protected void processCreate(Session session, Authorizable authorizable,
Map<String, RequestProperty> reqProperties,
List<Modification> changes) throws RepositoryException {
@NotNull
String path = authorizable.getPath();
for (RequestProperty prop : reqProperties.values()) {
String propName = prop.getName();
if (JcrConstants.JCR_PRIMARYTYPE.equals(propName) || JcrConstants.JCR_MIXINTYPES.equals(propName)) {
String parentPath = prop.getParentPath();
if (parentPath == null && JcrConstants.JCR_PRIMARYTYPE.equals(propName)) {
// don't allow changing the primaryType of the user/group root node
continue;
}
String tp = null;
if (parentPath == null || "/".equals(parentPath)) {
tp = path;
} else if (parentPath.startsWith("/")){
tp = Paths.get(path, parentPath.substring(1)).toString();
}
if (tp != null && (tp.equals(path) || Paths.get(tp).startsWith(path))) {
Node node = null;
if (session.nodeExists(tp)) {
node = session.getNode(tp);
} else {
Node tempNode = session.getNode(path);
// create any missing intermediate nodes
Iterator<Path> elements = Paths.get(parentPath).iterator();
while (elements.hasNext()) {
String segment = elements.next().toString();
String tempPath = Paths.get(tempNode.getPath(), segment).toString();
if (session.nodeExists(tempPath)) {
tempNode = session.getNode(tempPath);
} else {
String primaryType = getPrimaryType(reqProperties, tempPath);
if (primaryType != null) {
tempNode = tempNode.addNode(segment, primaryType);
} else {
tempNode = tempNode.addNode(segment);
}
changes.add(Modification.onCreated(tempNode.getPath()));
}
}
node = tempNode;
}
if (node != null) {
// only allow changing the primaryType of the ancestors, not the root
if (JcrConstants.JCR_PRIMARYTYPE.equals(propName)) {
if (tp.equals(path)) {
// don't allow changing the primaryType of the user home root
throw new AccessDeniedException("Access denied.");
} else {
final String nodeType = prop == null ? null : prop.getStringValues()[0];
if (nodeType != null && !node.isNodeType(nodeType)) {
node.setPrimaryType(nodeType);
changes.add(Modification.onModified(Paths.get(node.getPath(), propName).toString()));
}
}
} else if (JcrConstants.JCR_MIXINTYPES.equals(propName)) {
String[] mixins = (prop == null) || !prop.hasValues() ? null : prop.getStringValues();
if (mixins != null) {
for (final String mixin : mixins) {
if (!node.isNodeType(mixin)) {
node.addMixin(mixin);
changes.add(Modification.onModified(Paths.get(node.getPath(), propName).toString()));
}
}
}
}
}
}
}
}
}
/**
* Checks the collected content for a jcr:primaryType property at the
* specified path.
*
* @param path path to check
* @return the primary type or <code>null</code>
*/
private String getPrimaryType(Map<String, RequestProperty> reqProperties,
String path) {
RequestProperty prop = reqProperties.get(String.format("%s/%s", path, JcrConstants.JCR_PRIMARYTYPE));
return prop == null ? null : prop.getStringValues()[0];
}
/**
* Writes back the content
* @param session the sessioin to write the authorizable properties
* @param authorizable the authorizable to modify
* @param reqProperties the properties to write
* @param changes the list of changes which is supposed to be extended
*
* @throws RepositoryException if a repository error occurs
*/
protected void writeContent(Session session, Authorizable authorizable,
Collection<RequestProperty> reqProperties,
List<Modification> changes) throws RepositoryException {
for (RequestProperty prop : reqProperties) {
if (prop.hasValues()) {
// remove artificial "/" prepended to the prop path
String relativePath = prop.getPath().substring(1);
// skip jcr special properties
String name = prop.getName();
boolean isSpecialProp = name.equals(JcrConstants.JCR_PRIMARYTYPE)
|| name.equals(JcrConstants.JCR_MIXINTYPES);
if (authorizable.isGroup()) {
if (relativePath.equals("groupId")) {
// skip these
isSpecialProp = true;
}
} else {
if (relativePath.equals("userId")
|| relativePath.equals("pwd")
|| relativePath.equals("pwdConfirm")) {
// skip these
isSpecialProp = true;
}
}
if (!isSpecialProp && // skip these
!prop.isFileUpload()) { // don't handle files for user properties for now.
setPropertyAsIs(session, authorizable, prop, changes);
}
}
}
}
/**
* Find the PropertyDefinition for the specified propName
*
* @param propName the propertyName to check
* @param parentNode the parent node where the property will be set
* @return the property definition of the property or null if it could not be determined
*/
private @Nullable PropertyDefinition resolvePropertyDefinition(@NotNull String propName, @NotNull Node parentNode) throws RepositoryException {
NodeType primaryNodeType = parentNode.getPrimaryNodeType();
// try the primary type
PropertyDefinition propDef = resolvePropertyDefinition(propName, primaryNodeType);
if (propDef == null) {
// not found in the primary type, so try the mixins
NodeType[] mixinNodeTypes = parentNode.getMixinNodeTypes();
for (NodeType mixinNodeType : mixinNodeTypes) {
propDef = resolvePropertyDefinition(propName, mixinNodeType);
if (propDef != null) {
break;
}
}
}
return propDef;
}
/**
* Inspect the NodeType definition to try to determine the
* requiredType for the specified property
*
* @param propName the propertyName to check
* @param parentNode the parent node where the property will be set
* @return the required type of the property or {@link PropertyType#UNDEFINED} if it could not be determined
*/
private @Nullable PropertyDefinition resolvePropertyDefinition(@NotNull String propName, @NotNull NodeType nodeType) {
return Stream.of(nodeType.getPropertyDefinitions())
.filter(pd -> propName.equals(pd.getName()))
.findFirst()
.orElse(null);
}
/**
* set property without processing, except for type hints
*
* @param parent the parent node
* @param prop the request property
* @throws RepositoryException if a repository error occurs.
*/
private void setPropertyAsIs(Session session, Authorizable parent,
RequestProperty prop, List<Modification> changes)
throws RepositoryException {
String parentPath;
if (parent.isGroup()) {
parentPath = systemUserManagerPaths.getGroupPrefix()
+ parent.getID();
} else {
parentPath = systemUserManagerPaths.getUserPrefix()
+ parent.getID();
}
// no explicit typehint
int type = PropertyType.UNDEFINED;
boolean multiValue = false;
if (prop.getTypeHint() != null) {
try {
type = PropertyType.valueFromName(prop.getTypeHint());
multiValue = prop.hasMultiValueTypeHint();
} catch (Exception e) {
// ignore
}
} else {
// inspect the node type definitions to see if there is a known PropertyDefintion
// for the target property that we can get the required type from
String propParentPath = String.format("%s%s", parent.getPath(), prop.getParentPath());
if (session.nodeExists(propParentPath)) {
// try to determine required property type from the NodeType definition
Node parentNode = session.getNode(propParentPath);
@Nullable
PropertyDefinition propDef = resolvePropertyDefinition(prop.getName(), parentNode);
if (propDef != null) {
type = propDef.getRequiredType();
multiValue = propDef.isMultiple();
}
}
}
// remove artificial "/" prepended to the prop path
String relativePath = prop.getPath().substring(1);
String[] values = prop.getStringValues();
if (values == null) {
// remove property
boolean removedProp = removePropertyIfExists(parent, relativePath);
if (removedProp) {
changes.add(Modification.onDeleted(parentPath + "/"
+ relativePath));
}
} else if (values.length == 0) {
// do not create new prop here, but clear existing
if (parent.hasProperty(relativePath)) {
Value val = session.getValueFactory().createValue("");
parent.setProperty(relativePath, val);
changes.add(Modification.onModified(parentPath + "/"
+ relativePath));
}
} else if (values.length == 1) {
// if the provided value is the empty string, we don't have to do
// anything.
if (values[0].length() == 0) {
boolean removedProp = removePropertyIfExists(parent, relativePath);
if (removedProp) {
changes.add(Modification.onDeleted(parentPath + "/"
+ relativePath));
}
} else {
// modify property
if (type == PropertyType.DATE) {
// try conversion
Calendar c = dateParser.parse(values[0]);
if (c != null) {
if (multiValue) {
final Value[] array = new Value[1];
array[0] = session.getValueFactory().createValue(c);
parent.setProperty(relativePath, array);
changes.add(Modification.onModified(parentPath
+ "/" + relativePath));
} else {
Value cVal = session.getValueFactory().createValue(
c);
parent.setProperty(relativePath, cVal);
changes.add(Modification.onModified(parentPath
+ "/" + relativePath));
}
return;
}
// fall back to default behaviour
}
if (type == PropertyType.UNDEFINED) {
Value val = session.getValueFactory().createValue(
values[0], PropertyType.STRING);
parent.setProperty(relativePath, val);
} else {
if (multiValue) {
final Value[] array = new Value[1];
array[0] = session.getValueFactory().createValue(
values[0], type);
parent.setProperty(relativePath, array);
} else {
Value val = session.getValueFactory().createValue(
values[0], type);
parent.setProperty(relativePath, val);
}
}
changes.add(Modification.onModified(parentPath + "/"
+ relativePath));
}
} else {
if (type == PropertyType.DATE) {
// try conversion
ValueFactory valFac = session.getValueFactory();
Value[] c = dateParser.parse(values, valFac);
if (c != null) {
parent.setProperty(relativePath, c);
changes.add(Modification.onModified(parentPath + "/"
+ relativePath));
return;
}
// fall back to default behaviour
}
Value[] vals = new Value[values.length];
if (type == PropertyType.UNDEFINED) {
for (int i = 0; i < values.length; i++) {
vals[i] = session.getValueFactory().createValue(values[i]);
}
} else {
for (int i = 0; i < values.length; i++) {
vals[i] = session.getValueFactory().createValue(values[i],
type);
}
}
parent.setProperty(relativePath, vals);
changes.add(Modification.onModified(parentPath + "/"
+ relativePath));
}
}
/**
* Removes the property with the given name from the authorizable if it
* exists.
*
* @param authorizable the <code>org.apache.jackrabbit.api.security.user.Authorizable</code>
* that should have properties deleted.
* @param path the path of the property to remove
* @return path of the property that was removed or <code>null</code> if it
* was not removed
* @throws RepositoryException if a repository error occurs.
*/
private boolean removePropertyIfExists(Authorizable authorizable, String path)
throws RepositoryException {
if (authorizable.getProperty(path) != null) {
authorizable.removeProperty(path);
return true;
}
return false;
}
// ------ These methods were copied from AbstractSlingPostOperation ------
/**
* @param name the name
* @return <code>true</code> if the <code>name</code> starts with either of
* the prefixes {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT},
* {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT} and
* {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE}
*/
protected boolean hasItemPathPrefix(String name) {
return name.startsWith(SlingPostConstants.ITEM_PREFIX_ABSOLUTE)
|| name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT)
|| name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_PARENT);
}
/**
* @param properties the request parameters
* @return {@code true} if any of the request parameters starts with
* {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT}.
* In this case only parameters starting with either of the prefixes
* {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT},
* {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT}
* and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE} are
* considered as providing content to be stored. Otherwise all parameters
* not starting with the command prefix <code>:</code> are considered as
* parameters to be stored.
*/
protected final boolean requireItemPathPrefix(
Map<String, ?> properties) {
boolean requirePrefix = false;
Iterator<String> iterator = properties.keySet().iterator();
while (iterator.hasNext() && !requirePrefix) {
String name = iterator.next();
requirePrefix = name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT);
}
return requirePrefix;
}
protected String convertToString(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof String) {
return (String)obj;
} else if (obj instanceof String[]) {
String [] values = (String[])obj;
if (values.length > 0) {
return values[0];
}
return null;
} else if (obj instanceof RequestParameter) {
((RequestParameter)obj).getString();
} else if (obj instanceof RequestParameter[]) {
RequestParameter[] values = (RequestParameter[])obj;
if (values.length > 0) {
return values[0].getString();
}
return null;
}
return null;
}
protected @NotNull String[] convertToStringArray(Object obj) {
String [] strArray = null;
if (obj instanceof String) {
strArray = new String[] {(String)obj};
} else if (obj instanceof String[]) {
strArray = (String[])obj;
} else if (obj instanceof RequestParameter) {
strArray = new String[] {((RequestParameter)obj).getString()};
} else if (obj instanceof RequestParameter[]) {
RequestParameter[] values = (RequestParameter[])obj;
strArray = new String[values.length];
for (int i=0; i < values.length; i++) {
strArray[i] = values[i].getString();
}
}
return strArray == null ? new String[0] : strArray;
}
protected @NotNull RequestParameter[] convertToRequestParameterArray(Object obj) {
RequestParameter [] paramArray = null;
if (obj instanceof String) {
paramArray = new RequestParameter[] {
Builders.newRequestParameter(null, (String)obj)
};
} else if (obj instanceof String[]) {
String [] strValues = (String[])obj;
paramArray = new RequestParameter[strValues.length];
for (int i=0; i < strValues.length; i++) {
paramArray[i] = Builders.newRequestParameter(null, strValues[i]);
}
} else if (obj instanceof RequestParameter) {
paramArray = new RequestParameter[] {(RequestParameter)obj};
} else if (obj instanceof RequestParameter[]) {
paramArray = (RequestParameter[])obj;
}
return paramArray == null ? new RequestParameter[0] : paramArray;
}
}