Author: fmeschbe
Date: Fri Mar 11 12:32:19 2011
New Revision: 1080549
URL: http://svn.apache.org/viewvc?rev=1080549&view=rev
Log:
SLING-1983 Apply patch by Alex Klimetscheck (slightly modified, mostly
JavaDoc). Thanks alot.
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java?rev=1080549&r1=1080548&r2=1080549&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
(original)
+++
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
Fri Mar 11 12:32:19 2011
@@ -409,6 +409,39 @@ public interface SlingPostConstants {
public static final String SUFFIX_USE_DEFAULT_WHEN_MISSING =
"@UseDefaultWhenMissing";
/**
+ * Suffix indicating that a multi-value property is to be handled as an
+ * ordered set and the sent values start with either "+" or "-" to indicate
+ * wether a value should be added to or removed from the set.
+ * <p>
+ * If a property is marked to be patched with this suffix only properties
+ * whose value start with {@link #PATCH_ADD +} or {@link #PATCH_REMOVE -}
+ * are considered. Other values are ignored.
+ *
+ * @see #PATCH_ADD
+ * @see #PATCH_REMOVE
+ */
+ public static final String SUFFIX_PATCH = "@Patch";
+
+ /**
+ * Indicates a value to be added to the named multi-value property if the
+ * property is being #{@link #SUFFIX_PATCH patched}.
+ * <p>
+ * If the given value
+ * already exists amongst the values of the multi-value properties it is
+ * not added.
+ */
+ public static final char PATCH_ADD = '+';
+
+ /**
+ * Indicates a value to be removed from the named multi-value property if
+ * the property is being #{@link #SUFFIX_PATCH patched}.
+ * <p>
+ * If the given value exists multiple times amongst the values of the
+ * multi-value properties all occurrences are removed.
+ */
+ public static final char PATCH_REMOVE = '-';
+
+ /**
* Name of the request parameter containing the content to be imported
* by the 'import' operation.
*/
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java?rev=1080549&r1=1080548&r2=1080549&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
(original)
+++
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
Fri Mar 11 12:32:19 2011
@@ -63,6 +63,8 @@ public class RequestProperty {
private boolean useDefaultWhenMissing;
+ private boolean patch = false;
+
public RequestProperty(String path) {
assert path.startsWith("/");
this.path = ResourceUtil.normalize(path);
@@ -285,4 +287,16 @@ public class RequestProperty {
public void setUseDefaultWhenMissing(boolean b) {
useDefaultWhenMissing = b;
}
+
+ public void setPatch(boolean b) {
+ patch = b;
+ }
+
+ /**
+ * Returns whether this property is to be handled as a multi-value property
+ * seen as set.
+ */
+ public boolean isPatch() {
+ return patch;
+ }
}
\ No newline at end of file
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java?rev=1080549&r1=1080548&r2=1080549&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
(original)
+++
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
Fri Mar 11 12:32:19 2011
@@ -17,6 +17,7 @@
package org.apache.sling.servlets.post.impl.helper;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
@@ -31,6 +32,7 @@ import javax.jcr.ValueFactory;
import javax.jcr.nodetype.ConstraintViolationException;
import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.SlingPostConstants;
/**
* Sets a Property on the given Node, in some cases with a specific type and
@@ -195,8 +197,118 @@ public class SlingPropertyValueHandler {
*/
private void setPropertyAsIs(Node parent, RequestProperty prop)
throws RepositoryException {
- final ValueFactory valFac = parent.getSession().getValueFactory();
+ String[] values = prop.getStringValues();
+
+ if (values == null || (values.length == 1 && values[0].length() == 0))
{
+ // if no value is present or a single empty string is given,
+ // just remove the existing property (if any)
+ removeProperty(parent, prop);
+
+ } else if (values.length == 0) {
+ // do not create new prop here, but clear existing
+ clearProperty(parent, prop);
+
+ } else {
+ // when patching, simply update the value list using the patch
operations
+ if (prop.isPatch()) {
+ values = patch(parent, prop.getName(), values);
+ if (values == null) {
+ return;
+ }
+ }
+
+ final ValueFactory valFac = parent.getSession().getValueFactory();
+
+ final boolean multiValue = isMultiValue(parent, prop, values);
+ final int type = getType(parent, prop);
+
+ if (multiValue) {
+ // converting single into multi value props requires deleting
it first
+ removeIfSingleValueProperty(parent, prop);
+ }
+
+ if (type == PropertyType.DATE) {
+ if (storeAsDate(parent, prop.getName(), values, multiValue,
valFac)) {
+ return;
+ }
+ } else if (isReferencePropertyType(type)) {
+ if (storeAsReference(parent, prop.getName(), values, type,
multiValue, valFac)) {
+ return;
+ }
+ }
+
+ store(parent, prop.getName(), values, type, multiValue);
+ }
+ }
+
+ /**
+ * Patches a multi-value property using add and remove operations per
value.
+ */
+ private String[] patch(Node parent, String name, String[] values) throws
RepositoryException {
+ // we do not use a Set here, as we want to be very restrictive in our
+ // actions and avoid touching elements that are not modified through
the
+ // add/remove patch operations; e.g. if the value "foo" occurs twice
+ // in the existing array, and is not touched, afterwards there should
+ // still be two times "foo" in the list, even if this is not a real
set.
+ List<String> oldValues = new ArrayList<String>();
+
+ if (parent.hasProperty(name)) {
+ Property p = parent.getProperty(name);
+
+ // can only patch multi-value props
+ if (!p.getDefinition().isMultiple()) {
+ return null;
+ }
+
+ for (Value v : p.getValues()) {
+ oldValues.add(v.getString());
+ }
+ }
+
+ boolean modified = false;
+ for (String v : values) {
+ if (v != null && v.length() > 0) {
+ final char op = v.charAt(0);
+ final String val = v.substring(1);
+
+ if (op == SlingPostConstants.PATCH_ADD) {
+ if (!oldValues.contains(val)) {
+ oldValues.add(val);
+ modified = true;
+ }
+ } else if (op == SlingPostConstants.PATCH_REMOVE) {
+ while (oldValues.remove(val)) {
+ modified = true;
+ }
+ }
+ }
+ }
+
+ // if the patch does not include any operations (e.g. invalid ops)
+ // return null to indicate that nothing should be done
+ if (modified) {
+ return oldValues.toArray(new String[oldValues.size()]);
+ }
+
+ return null;
+ }
+
+
+ private boolean isReferencePropertyType(int propertyType) {
+ return propertyType == PropertyType.REFERENCE || propertyType ==
PROPERTY_TYPE_WEAKREFERENCE;
+ }
+
+ private boolean isWeakReference(int propertyType) {
+ return propertyType == PROPERTY_TYPE_WEAKREFERENCE;
+ }
+
+ /**
+ * Returns the property type to use for the given property. This is defined
+ * either by an explicit type hint in the request or simply the type of the
+ * existing property.
+ */
+ private int getType(Node parent, RequestProperty prop) throws
RepositoryException {
// no explicit typehint
int type = PropertyType.UNDEFINED;
if (prop.getTypeHint() != null) {
@@ -212,149 +324,160 @@ public class SlingPropertyValueHandler {
type = parent.getProperty(prop.getName()).getType();
}
}
+ return type;
+ }
- if (values == null) {
- // remove property
- final String removePath = removePropertyIfExists(parent,
prop.getName());
- if ( removePath != null ) {
- changes.add(Modification.onDeleted(removePath));
- }
- } else if (values.length == 0) {
- // do not create new prop here, but clear existing
- if (parent.hasProperty(prop.getName())) {
- if
(parent.getProperty(prop.getName()).getDefinition().isMultiple()) {
- //the existing property is multi-valued, so
just delete it?
- final String removePath = removePropertyIfExists(parent,
prop.getName());
- if ( removePath != null ) {
- changes.add(Modification.onDeleted(removePath));
- }
- } else {
- changes.add(Modification.onModified(
- parent.setProperty(prop.getName(), "").getPath()
- ));
- }
- }
- } else if (values.length == 1) {
- //if a MultiValueTypeHint is supplied, or the current value is
multiple,
- // store the updated property as multi-value.
- boolean storePropertyAsMultiValued = prop.hasMultiValueTypeHint();
- if (!prop.hasMultiValueTypeHint() &&
parent.hasProperty(prop.getName()) ) {
- //no type hint supplied, so check the current property
definition
- storePropertyAsMultiValued =
parent.getProperty(prop.getName()).getDefinition().isMultiple();
- }
+ /**
+ * Returns whether the property should be handled as multi-valued.
+ */
+ private boolean isMultiValue(Node parent, RequestProperty prop, String[]
values) throws RepositoryException {
+ // multiple values are provided
+ if (values != null && values.length > 1) {
+ return true;
+ }
+ // TypeHint with []
+ if (prop.hasMultiValueTypeHint()) {
+ return true;
+ }
+ // patch method requires multi value
+ if (prop.isPatch()) {
+ return true;
+ }
+ // nothing in the request, so check the current JCR property definition
+ if (parent.hasProperty(prop.getName()) ) {
+ return
parent.getProperty(prop.getName()).getDefinition().isMultiple();
+ }
+ return false;
+ }
- // if the provided value is the empty string, just remove the
existing property (if any).
- if ( values[0].length() == 0 ) {
+ /**
+ * Clears a property: sets an empty string for single-value properties, and
+ * removes multi-value properties.
+ */
+ private void clearProperty(Node parent, RequestProperty prop) throws
RepositoryException {
+ if (parent.hasProperty(prop.getName())) {
+ if
(parent.getProperty(prop.getName()).getDefinition().isMultiple()) {
+ //the existing property is multi-valued, so just delete it?
final String removePath = removePropertyIfExists(parent,
prop.getName());
if ( removePath != null ) {
changes.add(Modification.onDeleted(removePath));
}
} else {
- // modify property
- if (type == PropertyType.DATE) {
- // try conversion
- Calendar c = dateParser.parse(values[0]);
- if (c != null) {
- if ( storePropertyAsMultiValued ) {
- final Value[] array = new Value[1];
- array[0] =
parent.getSession().getValueFactory().createValue(c);
- changes.add(Modification.onModified(
- parent.setProperty(prop.getName(),
array).getPath()
- ));
- } else {
- changes.add(Modification.onModified(
- parent.setProperty(prop.getName(),
c).getPath()
- ));
- }
- return;
- }
- } else if (isReferencePropertyType(type)) {
- Value v = referenceParser.parse(values[0], valFac,
isWeakReference(type));
- if (v != null) {
- if ( storePropertyAsMultiValued ) {
- final Value[] array = new Value[] { v };
- changes.add(Modification.onModified(
- parent.setProperty(prop.getName(),
array).getPath()
- ));
- } else {
- changes.add(Modification.onModified(
- parent.setProperty(prop.getName(),
v).getPath()
- ));
- }
- return;
- }
+ changes.add(Modification.onModified(
+ parent.setProperty(prop.getName(), "").getPath()
+ ));
+ }
+ }
+ }
- }
+ /**
+ * Removes the property if it exists.
+ */
+ private void removeProperty(Node parent, RequestProperty prop) throws
RepositoryException {
+ final String removePath = removePropertyIfExists(parent,
prop.getName());
+ if ( removePath != null ) {
+ changes.add(Modification.onDeleted(removePath));
+ }
+ }
- // fall back to default behaviour
- final Property p;
- if ( type == PropertyType.UNDEFINED ) {
- if ( storePropertyAsMultiValued ) {
- final Value[] array = new Value[1];
- array[0] =
parent.getSession().getValueFactory().createValue(values[0]);
- p = parent.setProperty(prop.getName(), array);
- } else {
- p = parent.setProperty(prop.getName(), values[0]);
- }
- } else {
- if ( storePropertyAsMultiValued ) {
- final Value[] array = new Value[1];
- array[0] =
parent.getSession().getValueFactory().createValue(values[0], type);
- p = parent.setProperty(prop.getName(), array);
- } else {
- p = parent.setProperty(prop.getName(), values[0],
type);
- }
+ /**
+ * Removes the property if it exists and is single-valued.
+ */
+ private void removeIfSingleValueProperty(Node parent, RequestProperty
prop) throws RepositoryException {
+ if (parent.hasProperty(prop.getName())) {
+ if
(!parent.getProperty(prop.getName()).getDefinition().isMultiple()) {
+ // the existing property is single-valued, so we have to
delete it before setting the
+ // multi-value variation
+ final String removePath = removePropertyIfExists(parent,
prop.getName());
+ if ( removePath != null ) {
+ changes.add(Modification.onDeleted(removePath));
}
- changes.add(Modification.onModified(p.getPath()));
+ }
+ }
+ }
+
+ /**
+ * Stores property value(s) as date(s). Will parse the date(s) from the
string
+ * value(s) in the {@link RequestProperty}.
+ *
+ * @return true only if parsing was successfull and the property was
actually changed
+ */
+ private boolean storeAsDate(Node parent, String name, String[] values,
boolean multiValued, ValueFactory valFac) throws RepositoryException {
+ if (multiValued) {
+ Value[] array = dateParser.parse(values, valFac);
+ if (array != null) {
+ changes.add(Modification.onModified(
+ parent.setProperty(name, array).getPath()
+ ));
+ return true;
}
} else {
- if (parent.hasProperty(prop.getName())) {
- if
(!parent.getProperty(prop.getName()).getDefinition().isMultiple()) {
- //the existing property is single-valued, so we
have to delete it before setting the
- // multi-value variation
- final String removePath = removePropertyIfExists(parent,
prop.getName());
- if ( removePath != null ) {
- changes.add(Modification.onDeleted(removePath));
- }
- }
- }
-
- if (type == PropertyType.DATE) {
- // try conversion
- Value[] c = dateParser.parse(values, valFac);
+ if (values.length >= 1) {
+ Calendar c = dateParser.parse(values[0]);
if (c != null) {
changes.add(Modification.onModified(
- parent.setProperty(prop.getName(), c).getPath()
+ parent.setProperty(name, c).getPath()
));
- return;
+ return true;
}
- } else if (isReferencePropertyType(type)) {
- // try conversion
- Value[] n = referenceParser.parse(values, valFac,
isWeakReference(type));
- if (n != null) {
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stores property value(s) as reference(s). Will parse the reference(s)
from the string
+ * value(s) in the {@link RequestProperty}.
+ *
+ * @return true only if parsing was successfull and the property was
actually changed
+ */
+ private boolean storeAsReference(Node parent, String name, String[]
values, int type, boolean multiValued, ValueFactory valFac) throws
RepositoryException {
+ if (multiValued) {
+ Value[] array = referenceParser.parse(values, valFac,
isWeakReference(type));
+ if (array != null) {
+ changes.add(Modification.onModified(
+ parent.setProperty(name, array).getPath()
+ ));
+ return true;
+ }
+ } else {
+ if (values.length >= 1) {
+ Value v = referenceParser.parse(values[0], valFac,
isWeakReference(type));
+ if (v != null) {
changes.add(Modification.onModified(
- parent.setProperty(prop.getName(), n).getPath()
+ parent.setProperty(name, v).getPath()
));
- return;
+ return true;
}
}
- // fall back to default behaviour
- final Property p;
- if ( type == PropertyType.UNDEFINED ) {
- p = parent.setProperty(prop.getName(), values);
- } else {
- p = parent.setProperty(prop.getName(), values, type);
- }
- changes.add(Modification.onModified(p.getPath()));
}
+ return false;
}
- private boolean isReferencePropertyType(int propertyType) {
- return propertyType == PropertyType.REFERENCE || propertyType ==
PROPERTY_TYPE_WEAKREFERENCE;
- }
+ /**
+ * Stores the property as string or via a strign value, but with an
explicit
+ * type. Both multi-value or single-value.
+ */
+ private void store(Node parent, String name, String[] values, int type,
boolean multiValued /*, ValueFactory valFac */) throws RepositoryException {
+ Property p = null;
- private boolean isWeakReference(int propertyType) {
- return propertyType == PROPERTY_TYPE_WEAKREFERENCE;
+ if (multiValued) {
+ if (type == PropertyType.UNDEFINED) {
+ p = parent.setProperty(name, values);
+ } else {
+ p = parent.setProperty(name, values, type);
+ }
+ } else if (values.length >= 1) {
+ if (type == PropertyType.UNDEFINED) {
+ p = parent.setProperty(name, values[0]);
+ } else {
+ p = parent.setProperty(name, values[0], type);
+ }
+ }
+
+ if (p != null) {
+ changes.add(Modification.onModified(p.getPath()));
+ }
}
/**
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java?rev=1080549&r1=1080548&r2=1080549&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java
(original)
+++
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java
Fri Mar 11 12:32:19 2011
@@ -299,6 +299,21 @@ abstract class AbstractCreateOperation e
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)) {
+ RequestProperty prop = getOrCreateRequestProperty(
+ reqProperties, propPath,
+ SlingPostConstants.SUFFIX_PATCH);
+
+ prop.setPatch(true);
+
+ continue;
+ }
// plain property, create from values
RequestProperty prop = getOrCreateRequestProperty(reqProperties,