| /* |
| * 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.servlets.post.impl.operations; |
| |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| import javax.jcr.Node; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.nodetype.NodeType; |
| import javax.servlet.ServletException; |
| |
| 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.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.ResourceUtil; |
| import org.apache.sling.servlets.post.Modification; |
| import org.apache.sling.servlets.post.NodeNameGenerator; |
| import org.apache.sling.servlets.post.PostResponse; |
| import org.apache.sling.servlets.post.SlingPostConstants; |
| import org.apache.sling.servlets.post.VersioningConfiguration; |
| import org.apache.sling.servlets.post.impl.helper.Chunk; |
| import org.apache.sling.servlets.post.impl.helper.DefaultNodeNameGenerator; |
| import org.apache.sling.servlets.post.impl.helper.RequestProperty; |
| |
| abstract class AbstractCreateOperation extends AbstractPostOperation { |
| private final Random randomCollisionIndex = new Random(); |
| |
| /** |
| * The default node name generator |
| */ |
| private NodeNameGenerator defaultNodeNameGenerator; |
| |
| /** |
| * utility class for generating node names |
| */ |
| private NodeNameGenerator[] extraNodeNameGenerators; |
| |
| /** |
| * regular expression for parameters to ignore |
| */ |
| private Pattern ignoredParameterNamePattern; |
| |
| protected AbstractCreateOperation() { |
| this.defaultNodeNameGenerator = new DefaultNodeNameGenerator(); |
| this.ignoredParameterNamePattern = null; |
| } |
| |
| public void setDefaultNodeNameGenerator( |
| NodeNameGenerator defaultNodeNameGenerator) { |
| this.defaultNodeNameGenerator = defaultNodeNameGenerator; |
| } |
| |
| public void setExtraNodeNameGenerators( |
| NodeNameGenerator[] extraNodeNameGenerators) { |
| this.extraNodeNameGenerators = extraNodeNameGenerators; |
| } |
| |
| public void setIgnoredParameterNamePattern( |
| final Pattern ignoredParameterNamePattern) { |
| this.ignoredParameterNamePattern = ignoredParameterNamePattern; |
| } |
| |
| /** |
| * Returns true if any of the request parameters starts with |
| * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>}. |
| * In this case only parameters starting with either of the prefixes |
| * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>}, |
| * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT <code>../</code>} |
| * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE <code>/</code>} 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. |
| * |
| * @param request The http request |
| * @return If a prefix is required. |
| */ |
| private final boolean requireItemPathPrefix( |
| SlingHttpServletRequest request) { |
| |
| boolean requirePrefix = false; |
| |
| Enumeration<?> names = request.getParameterNames(); |
| while (names.hasMoreElements() && !requirePrefix) { |
| String name = (String) names.nextElement(); |
| requirePrefix = name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT); |
| } |
| |
| return requirePrefix; |
| } |
| |
| |
| |
| /** |
| * Returns <code>true</code> if the <code>name</code> starts with either |
| * of the prefixes |
| * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>}, |
| * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT <code>../</code>} |
| * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE <code>/</code>}. |
| * |
| * @param name The name |
| * @return {@code true} if the name has a prefix |
| */ |
| private 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); |
| } |
| |
| /** |
| * Create node(s) according to current request |
| * |
| * @throws RepositoryException if a repository error occurs |
| */ |
| protected void processCreate(final ResourceResolver resolver, |
| final Map<String, RequestProperty> reqProperties, |
| final PostResponse response, |
| final List<Modification> changes, |
| final VersioningConfiguration versioningConfiguration) |
| throws PersistenceException, RepositoryException { |
| |
| final String path = response.getPath(); |
| final Resource resource = resolver.getResource(path); |
| |
| if ( resource == null || ResourceUtil.isSyntheticResource(resource) ) { |
| deepGetOrCreateResource(resolver, path, reqProperties, changes, versioningConfiguration); |
| response.setCreateRequest(true); |
| |
| } else { |
| updateNodeType(resolver, path, reqProperties, changes, versioningConfiguration); |
| updateMixins(resolver, path, reqProperties, changes, versioningConfiguration); |
| } |
| } |
| |
| private boolean isVersionable(final Node node) throws RepositoryException { |
| return node.isNodeType("mix:versionable"); |
| } |
| |
| protected void updateNodeType(final ResourceResolver resolver, |
| final String path, |
| final Map<String, RequestProperty> reqProperties, |
| final List<Modification> changes, |
| final VersioningConfiguration versioningConfiguration) |
| throws PersistenceException, RepositoryException { |
| final String nodeType = getPrimaryType(reqProperties, path); |
| if (nodeType != null) { |
| final Resource rsrc = resolver.getResource(path); |
| final ModifiableValueMap mvm = rsrc.adaptTo(ModifiableValueMap.class); |
| if ( mvm != null ) { |
| final Node node = rsrc.adaptTo(Node.class); |
| final boolean wasVersionable = (node == null ? false : isVersionable(node)); |
| |
| if ( node != null ) { |
| this.jcrSsupport.checkoutIfNecessary(rsrc, changes, versioningConfiguration); |
| node.setPrimaryType(nodeType); |
| } else { |
| mvm.put("jcr:primaryType", nodeType); |
| } |
| |
| if ( node != null ) { |
| // this is a bit of a cheat; there isn't a formal checkout, but assigning |
| // the mix:versionable mixin does an implicit checkout |
| if (!wasVersionable && |
| versioningConfiguration.isCheckinOnNewVersionableNode() && |
| isVersionable(node)) { |
| changes.add(Modification.onCheckout(path)); |
| } |
| } |
| } |
| } |
| } |
| |
| protected void updateMixins(final ResourceResolver resolver, |
| final String path, |
| final Map<String, RequestProperty> reqProperties, |
| final List<Modification> changes, |
| final VersioningConfiguration versioningConfiguration) |
| throws PersistenceException, RepositoryException { |
| final String[] mixins = getMixinTypes(reqProperties, path); |
| if (mixins != null) { |
| |
| final Resource rsrc = resolver.getResource(path); |
| final ModifiableValueMap mvm = rsrc.adaptTo(ModifiableValueMap.class); |
| if ( mvm != null ) { |
| final Node node = rsrc.adaptTo(Node.class); |
| |
| final Set<String> newMixins = new HashSet<>(); |
| newMixins.addAll(Arrays.asList(mixins)); |
| |
| // clear existing mixins first |
| if ( node != null ) { |
| this.jcrSsupport.checkoutIfNecessary(rsrc, changes, versioningConfiguration); |
| for (NodeType mixin : node.getMixinNodeTypes()) { |
| String mixinName = mixin.getName(); |
| if (!newMixins.remove(mixinName)) { |
| node.removeMixin(mixinName); |
| } |
| } |
| |
| // add new mixins |
| for (String mixin : newMixins) { |
| node.addMixin(mixin); |
| // this is a bit of a cheat; there isn't a formal checkout, but assigning |
| // the mix:versionable mixin does an implicit checkout |
| if (mixin.equals("mix:versionable") && |
| versioningConfiguration.isCheckinOnNewVersionableNode()) { |
| changes.add(Modification.onCheckout(path)); |
| } |
| } |
| } else { |
| mvm.put("jcr:mixinTypes", mixins); |
| } |
| |
| } |
| } |
| } |
| |
| /** |
| * Collects the properties that form the content to be written back to the |
| * resource tree. |
| * |
| * @throws RepositoryException if a repository error occurs |
| * @throws ServletException if an internal error occurs |
| */ |
| protected Map<String, RequestProperty> collectContent( |
| final SlingHttpServletRequest request, |
| final PostResponse response) { |
| |
| final boolean requireItemPrefix = requireItemPathPrefix(request); |
| |
| // walk the request parameters and collect the properties |
| final LinkedHashMap<String, RequestProperty> reqProperties = new LinkedHashMap<>(); |
| for (final Map.Entry<String, RequestParameter[]> e : request.getRequestParameterMap().entrySet()) { |
| final String paramName = e.getKey(); |
| |
| if (ignoreParameter(paramName)) { |
| continue; |
| } |
| |
| // skip parameters that do not start with the save prefix |
| if (requireItemPrefix && !hasItemPathPrefix(paramName)) { |
| continue; |
| } |
| |
| // ensure the paramName is an absolute property name |
| final String propPath = toPropertyPath(paramName, response); |
| |
| // @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)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.TYPE_HINT_SUFFIX); |
| |
| final RequestParameter[] rp = e.getValue(); |
| if (rp.length > 0) { |
| prop.setTypeHintValue(rp[0].getString()); |
| } |
| |
| continue; |
| } |
| |
| // @DefaultValue |
| if (propPath.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.DEFAULT_VALUE_SUFFIX); |
| |
| prop.setDefaultValues(e.getValue()); |
| |
| continue; |
| } |
| |
| // 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. |
| if (propPath.endsWith(SlingPostConstants.VALUE_FROM_SUFFIX)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.VALUE_FROM_SUFFIX); |
| |
| // @ValueFrom params must have exactly one value, else ignored |
| if (e.getValue().length == 1) { |
| final String refName = e.getValue()[0].getString(); |
| final RequestParameter[] refValues = request.getRequestParameters(refName); |
| if (refValues != null) { |
| prop.setValues(refValues); |
| } |
| } |
| |
| continue; |
| } |
| |
| // 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 |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_DELETE)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, SlingPostConstants.SUFFIX_DELETE); |
| |
| prop.setDelete(true); |
| |
| continue; |
| } |
| |
| // 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. |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_MOVE_FROM)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_MOVE_FROM); |
| |
| // @MoveFrom params must have exactly one value, else ignored |
| if (e.getValue().length == 1) { |
| final String sourcePath = e.getValue()[0].getString(); |
| prop.setRepositorySource(sourcePath, true); |
| } |
| |
| continue; |
| } |
| |
| // 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. |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_COPY_FROM)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_COPY_FROM); |
| |
| // @MoveFrom params must have exactly one value, else ignored |
| if (e.getValue().length == 1) { |
| final String sourcePath = e.getValue()[0].getString(); |
| prop.setRepositorySource(sourcePath, false); |
| } |
| |
| continue; |
| } |
| |
| // SLING-1412: @IgnoreBlanks |
| // @Ignore example: |
| // <input name="./Text" type="hidden" value="test" /> |
| // <input name="./Text" type="hidden" value="" /> |
| // <input name="./Text@String[]" type="hidden" value="true" /> |
| // <input name="./Text@IgnoreBlanks" type="hidden" value="true" /> |
| // causes the JCR Text property to be set by copying the /tmp/path |
| // property to Text. |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_IGNORE_BLANKS)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_IGNORE_BLANKS); |
| |
| if (e.getValue().length == 1) { |
| prop.setIgnoreBlanks(true); |
| } |
| |
| continue; |
| } |
| |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_USE_DEFAULT_WHEN_MISSING)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_USE_DEFAULT_WHEN_MISSING); |
| |
| if (e.getValue().length == 1) { |
| prop.setUseDefaultWhenMissing(true); |
| } |
| |
| continue; |
| } |
| // @Patch |
| // Example: |
| // <input name="tags@TypeHint" value="String[]" type="hidden" /> |
| // <input name="tags@Patch" value="true" type="hidden" /> |
| // <input name="tags" value="+apple" type="hidden" /> |
| // <input name="tags" value="-orange" type="hidden" /> |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_PATCH)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_PATCH); |
| |
| prop.setPatch(true); |
| |
| continue; |
| } |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_OFFSET)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_OFFSET); |
| if (e.getValue().length == 1) { |
| Chunk chunk = prop.getChunk(); |
| if(chunk == null){ |
| chunk = new Chunk(); |
| } |
| chunk.setOffsetValue(Long.parseLong(e.getValue()[0].toString())); |
| prop.setChunk(chunk); |
| } |
| continue; |
| } |
| |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_COMPLETED)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_COMPLETED); |
| if (e.getValue().length == 1) { |
| Chunk chunk = prop.getChunk(); |
| if(chunk == null){ |
| chunk = new Chunk(); |
| } |
| chunk.setCompleted(Boolean.parseBoolean((e.getValue()[0].toString()))); |
| prop.setChunk(chunk); |
| } |
| continue; |
| } |
| |
| if (propPath.endsWith(SlingPostConstants.SUFFIX_LENGTH)) { |
| final RequestProperty prop = getOrCreateRequestProperty( |
| reqProperties, propPath, |
| SlingPostConstants.SUFFIX_LENGTH); |
| if (e.getValue().length == 1) { |
| Chunk chunk = prop.getChunk(); |
| if(chunk == null){ |
| chunk = new Chunk(); |
| } |
| chunk.setLength(Long.parseLong(e.getValue()[0].toString())); |
| prop.setChunk(chunk); |
| } |
| continue; |
| } |
| |
| // plain property, create from values |
| final RequestProperty prop = getOrCreateRequestProperty(reqProperties, |
| propPath, null); |
| prop.setValues(e.getValue()); |
| } |
| |
| return reqProperties; |
| } |
| |
| /** |
| * Returns <code>true</code> if the parameter of the given name should be |
| * ignored. |
| */ |
| private boolean ignoreParameter(final String paramName) { |
| // do not store parameters with names starting with sling:post |
| if (paramName.startsWith(SlingPostConstants.RP_PREFIX)) { |
| return true; |
| } |
| |
| // SLING-298: skip form encoding parameter |
| if (paramName.equals("_charset_")) { |
| return true; |
| } |
| |
| // SLING-2120: ignore parameter match ignoredParameterNamePattern |
| if (this.ignoredParameterNamePattern != null |
| && this.ignoredParameterNamePattern.matcher(paramName).matches()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the <code>paramName</code> as an absolute (unnormalized) property |
| * path by prepending the response path (<code>response.getPath</code>) to |
| * the parameter name if not already absolute. |
| */ |
| private String toPropertyPath(String paramName, PostResponse response) { |
| if (!paramName.startsWith("/")) { |
| paramName = ResourceUtil.normalize(response.getPath() + '/' + paramName); |
| } |
| |
| return paramName; |
| } |
| |
| /** |
| * 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. |
| * @param paramName 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 paramName, String suffix) { |
| if (suffix != null && paramName.endsWith(suffix)) { |
| paramName = paramName.substring(0, paramName.length() |
| - suffix.length()); |
| } |
| |
| RequestProperty prop = props.get(paramName); |
| if (prop == null) { |
| prop = new RequestProperty(paramName); |
| props.put(paramName, prop); |
| } |
| |
| return prop; |
| } |
| |
| |
| /** |
| * Deep gets or creates a resource, parent-padding with default resources. If |
| * the path is empty, the given parent resource is returned. |
| * |
| * @param path path to resources that needs to be deep-created |
| * @return Resource at path |
| * @throws PersistenceException if an error occurs |
| * @throws IllegalArgumentException if the path is relative and parent is |
| * <code>null</code> |
| */ |
| protected Resource deepGetOrCreateResource(final ResourceResolver resolver, |
| final String path, |
| final Map<String, RequestProperty> reqProperties, |
| final List<Modification> changes, |
| final VersioningConfiguration versioningConfiguration) |
| throws PersistenceException, RepositoryException { |
| if (log.isDebugEnabled()) { |
| log.debug("Deep-creating resource '{}'", path); |
| } |
| if (path == null || !path.startsWith("/")) { |
| throw new IllegalArgumentException("path must be an absolute path."); |
| } |
| // get the starting resource |
| String startingResourcePath = path; |
| Resource startingResource = null; |
| while (startingResource == null) { |
| if (startingResourcePath.equals("/")) { |
| startingResource = resolver.getResource("/"); |
| if (startingResource == null){ |
| throw new PersistenceException("Access denied for root resource, resource can't be created: " + path); |
| } |
| } else { |
| final Resource r = resolver.getResource(startingResourcePath); |
| if ( r != null && !ResourceUtil.isSyntheticResource(r)) { |
| startingResource = resolver.getResource(startingResourcePath); |
| updateNodeType(resolver, startingResourcePath, reqProperties, changes, versioningConfiguration); |
| updateMixins(resolver, startingResourcePath, reqProperties, changes, versioningConfiguration); |
| } else { |
| int pos = startingResourcePath.lastIndexOf('/'); |
| if (pos > 0) { |
| startingResourcePath = startingResourcePath.substring(0, pos); |
| } else { |
| startingResourcePath = "/"; |
| } |
| } |
| } |
| } |
| // is the searched resource already existing? |
| if (startingResourcePath.length() == path.length()) { |
| return startingResource; |
| } |
| // create nodes |
| int from = (startingResourcePath.length() == 1 |
| ? 1 |
| : startingResourcePath.length() + 1); |
| Resource resource = startingResource; |
| while (from > 0) { |
| final int to = path.indexOf('/', from); |
| final String name = to < 0 ? path.substring(from) : path.substring( |
| from, to); |
| // although the resource should not exist (according to the first test |
| // above) |
| // we do a sanety check. |
| final Resource child = resource.getChild(name); |
| if (child != null && !ResourceUtil.isSyntheticResource(child)) { |
| resource = child; |
| updateNodeType(resolver, resource.getPath(), reqProperties, changes, versioningConfiguration); |
| updateMixins(resolver, resource.getPath(), reqProperties, changes, versioningConfiguration); |
| } else { |
| final String tmpPath = to < 0 ? path : path.substring(0, to); |
| // check for node type |
| final String nodeType = getPrimaryType(reqProperties, tmpPath); |
| |
| this.jcrSsupport.checkoutIfNecessary(resource, changes, versioningConfiguration); |
| |
| try { |
| final Map<String, Object> props = new HashMap<>(); |
| if (nodeType != null) { |
| props.put("jcr:primaryType", nodeType); |
| } |
| // check for mixin types |
| final String[] mixinTypes = getMixinTypes(reqProperties, |
| tmpPath); |
| if (mixinTypes != null) { |
| props.put("jcr:mixinTypes", mixinTypes); |
| } |
| |
| resource = resolver.create(resource, name, props); |
| } catch (final PersistenceException e) { |
| log.error("Unable to create resource named " + name + " in " + resource.getPath()); |
| throw e; |
| } |
| changes.add(Modification.onCreated(resource.getPath())); |
| } |
| from = to + 1; |
| } |
| return resource; |
| } |
| |
| /** |
| * 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(path + "/jcr:primaryType"); |
| return prop == null ? null : prop.getStringValues()[0]; |
| } |
| |
| /** |
| * Checks the collected content for a jcr:mixinTypes property at the |
| * specified path. |
| * |
| * @param path path to check |
| * @return the mixin types or <code>null</code> |
| */ |
| private String[] getMixinTypes(Map<String, RequestProperty> reqProperties, |
| String path) { |
| RequestProperty prop = reqProperties.get(path + "/jcr:mixinTypes"); |
| return (prop == null) || !prop.hasValues() ? null : prop.getStringValues(); |
| } |
| |
| |
| protected String generateName(SlingHttpServletRequest request, String basePath) |
| throws RepositoryException { |
| |
| // SLING-1091: If a :name parameter is supplied, the (first) value of this parameter is used unmodified as the name |
| // for the new node. If the name is illegally formed with respect to JCR name requirements, an exception will be |
| // thrown when trying to create the node. The assumption with the :name parameter is, that the caller knows what |
| // he (or she) is supplying and should get the exact result if possible. |
| RequestParameterMap parameters = request.getRequestParameterMap(); |
| RequestParameter specialParam = parameters.getValue(SlingPostConstants.RP_NODE_NAME); |
| if ( specialParam != null ) { |
| if ( specialParam.getString() != null && specialParam.getString().length() > 0 ) { |
| // If the path ends with a *, create a node under its parent, with |
| // a generated node name |
| basePath = basePath += "/" + specialParam.getString(); |
| |
| // if the resulting path already exists then report an error |
| if (request.getResourceResolver().getResource(basePath) != null) { |
| throw new RepositoryException( |
| "Collision in node names for path=" + basePath); |
| } |
| |
| return basePath; |
| } |
| } |
| |
| // no :name value was supplied, so generate a name |
| boolean requirePrefix = requireItemPathPrefix(request); |
| |
| String generatedName = null; |
| if (extraNodeNameGenerators != null) { |
| for (NodeNameGenerator generator : extraNodeNameGenerators) { |
| generatedName = generator.getNodeName(request, basePath, requirePrefix, defaultNodeNameGenerator); |
| if (generatedName != null) { |
| break; |
| } |
| } |
| } |
| if (generatedName == null) { |
| generatedName = defaultNodeNameGenerator.getNodeName(request, basePath, requirePrefix, defaultNodeNameGenerator); |
| } |
| |
| // If the path ends with a *, create a node under its parent, with |
| // a generated node name |
| basePath += "/" + generatedName; |
| |
| basePath = ensureUniquePath(request, basePath); |
| |
| return basePath; |
| } |
| |
| /** Generate a unique path in case the node name generator didn't */ |
| private String ensureUniquePath(SlingHttpServletRequest request, String basePath) throws RepositoryException { |
| // if resulting path exists, add a suffix until it's not the case |
| // anymore |
| final ResourceResolver resolver = request.getResourceResolver(); |
| |
| // if resulting path exists, add a random suffix until it's not the case |
| // anymore |
| final int MAX_TRIES = 1000; |
| if (resolver.getResource(basePath) != null ) { |
| for(int i=0; i < MAX_TRIES; i++) { |
| final int uniqueIndex = Math.abs(randomCollisionIndex.nextInt()); |
| String newPath = basePath + "_" + uniqueIndex; |
| if (resolver.getResource(newPath) == null) { |
| basePath = basePath + "_" + uniqueIndex; |
| basePath = newPath; |
| break; |
| } |
| } |
| |
| // Give up after MAX_TRIES |
| if (resolver.getResource(basePath) != null ) { |
| throw new RepositoryException( |
| "Collision in generated node names under " + basePath + ", generated path " + basePath + " already exists"); |
| } |
| } |
| |
| return basePath; |
| } |
| } |