blob: 9c5769438a742fdacafef524ad7a0194535c8975 [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.servlets.post.impl.helper;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import org.apache.sling.api.servlets.HtmlResponse;
/**
* Sets a Property on the given Node, in some cases with a specific type and
* value. For example, "lastModified" with an empty value is stored as the
* current Date.
*/
public class SlingPropertyValueHandler {
/**
* Defins a map of auto properties
*/
private static final Map<String, AutoType> AUTO_PROPS = new HashMap<String, AutoType>();
static {
AUTO_PROPS.put("created", AutoType.CREATED);
AUTO_PROPS.put("createdBy", AutoType.CREATED_BY);
AUTO_PROPS.put("jcr:created", AutoType.CREATED);
AUTO_PROPS.put("jcr:createdBy", AutoType.CREATED_BY);
AUTO_PROPS.put("lastModified", AutoType.MODIFIED);
AUTO_PROPS.put("lastModifiedBy", AutoType.MODIFIED_BY);
AUTO_PROPS.put("jcr:lastModified", AutoType.MODIFIED);
AUTO_PROPS.put("jcr:lastModifiedBy", AutoType.MODIFIED_BY);
}
/**
* the post processor
*/
private final HtmlResponse response;
private final DateParser dateParser;
/**
* current date for all properties in this request
*/
private final Calendar now = Calendar.getInstance();
/**
* Constructs a propert value handler
*/
public SlingPropertyValueHandler(DateParser dateParser, HtmlResponse response) {
this.dateParser = dateParser;
this.response = response;
}
/**
* Set property on given node, with some automatic values when user provides
* the field name but no value.
*
* html example for testing:
* <xmp>
* <input type="hidden" name="created"/>
* <input type="hidden" name="lastModified"/>
* <input type="hidden" name="createdBy" />
* <input type="hidden" name="lastModifiedBy"/>
* </xmp>
*
* @param parent the parent node
* @param prop the request property
* @throws RepositoryException if a repository error occurs
*/
public void setProperty(Node parent, RequestProperty prop)
throws RepositoryException {
final String name = prop.getName();
if (prop.providesValue()) {
// if user provided a value, don't mess with it
setPropertyAsIs(parent, prop);
} else if (AUTO_PROPS.containsKey(name)) {
// avoid collision with protected properties
switch (AUTO_PROPS.get(name)) {
case CREATED:
if (parent.isNew()) {
setCurrentDate(parent, name);
}
break;
case CREATED_BY:
if (parent.isNew()) {
setCurrentUser(parent, name);
}
break;
case MODIFIED:
setCurrentDate(parent, name);
break;
case MODIFIED_BY:
setCurrentUser(parent, name);
break;
}
} else {
// no magic field, set value as provided
setPropertyAsIs(parent, prop);
}
}
/**
* Sets the property to the given date
* @param parent parent node
* @param name name of the property
* @throws RepositoryException if a repository error occurs
*/
private void setCurrentDate(Node parent, String name)
throws RepositoryException {
removePropertyIfExists(parent, name);
response.onModified(
parent.setProperty(name, now).getPath()
);
}
/**
* set property to the current User id
* @param parent parent node
* @param name name of the property
* @throws RepositoryException if a repository error occurs
*/
private void setCurrentUser(Node parent, String name)
throws RepositoryException {
removePropertyIfExists(parent, name);
response.onModified(
parent.setProperty(name, parent.getSession().getUserID()).getPath()
);
}
/**
* Removes the property with the given name from the parent node if it
* exists and if it's not a mandatory property.
*
* @param parent the parent node
* @param name the name 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 String removePropertyIfExists(Node parent, String name)
throws RepositoryException {
if (parent.hasProperty(name)) {
Property prop = parent.getProperty(name);
if (!prop.getDefinition().isMandatory()) {
String path = prop.getPath();
prop.remove();
return path;
}
}
return 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(Node parent, RequestProperty prop)
throws RepositoryException {
// no explicit typehint
int type = PropertyType.UNDEFINED;
if (prop.getTypeHint() != null) {
try {
type = PropertyType.valueFromName(prop.getTypeHint());
} catch (Exception e) {
// ignore
}
}
String[] values = prop.getStringValues();
if (values == null) {
// remove property
response.onDeleted(
removePropertyIfExists(parent, prop.getName())
);
} else if (values.length == 0) {
// do not create new prop here, but clear existing
if (parent.hasProperty(prop.getName())) {
response.onModified(
parent.setProperty(prop.getName(), "").getPath()
);
}
} else if (values.length == 1) {
final String removePath = removePropertyIfExists(parent, prop.getName());
// if the provided value is the empty string, we don't have to do anything.
if ( values[0].length() == 0 ) {
if ( removePath != null ) {
response.onDeleted(removePath);
}
} else {
// modify property
if (type == PropertyType.DATE) {
// try conversion
Calendar c = dateParser.parse(values[0]);
if (c != null) {
if ( prop.hasMultiValueTypeHint() ) {
final Value[] array = new Value[1];
array[0] = parent.getSession().getValueFactory().createValue(c);
response.onModified(
parent.setProperty(prop.getName(), array).getPath()
);
} else {
response.onModified(
parent.setProperty(prop.getName(), c).getPath()
);
}
return;
}
// fall back to default behaviour
}
final Property p;
if ( type == PropertyType.UNDEFINED ) {
p = parent.setProperty(prop.getName(), values[0]);
} else {
if ( prop.hasMultiValueTypeHint() ) {
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);
}
}
response.onModified(p.getPath());
}
} else {
removePropertyIfExists(parent, prop.getName());
if (type == PropertyType.DATE) {
// try conversion
ValueFactory valFac = parent.getSession().getValueFactory();
Value[] c = dateParser.parse(values, valFac);
if (c != null) {
response.onModified(
parent.setProperty(prop.getName(), c).getPath()
);
return;
}
// 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);
}
response.onModified(p.getPath());
}
}
/**
* Defines an auto property behavior
*/
private enum AutoType {
CREATED,
CREATED_BY,
MODIFIED,
MODIFIED_BY
}
}