blob: ded63e6e2fc72129398252d1a9da6166d1585efb [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.jackrabbit.oak.jcr.session;
import static com.google.common.collect.Lists.newArrayListWithCapacity;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.List;
import javax.jcr.AccessDeniedException;
import javax.jcr.Binary;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.version.OnParentVersionAction;
import javax.jcr.version.VersionException;
import org.apache.jackrabbit.oak.api.Tree.Status;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
import org.apache.jackrabbit.oak.jcr.delegate.PropertyDelegate;
import org.apache.jackrabbit.oak.jcr.session.operation.PropertyOperation;
import org.apache.jackrabbit.oak.plugins.value.jcr.PartialValueFactory;
import org.apache.jackrabbit.value.ValueHelper;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TODO document
*/
public class PropertyImpl extends ItemImpl<PropertyDelegate> implements Property {
private static final Logger LOG = LoggerFactory.getLogger(PropertyImpl.class);
private static final Value[] NO_VALUES = new Value[0];
PropertyImpl(PropertyDelegate dlg, SessionContext sessionContext) {
super(dlg, sessionContext);
}
//---------------------------------------------------------------< Item >---
@Override
public boolean isNode() {
return false;
}
@Override
@NotNull
public Node getParent() throws RepositoryException {
return perform(new PropertyOperation<Node>(dlg, "getParent") {
@NotNull
@Override
public Node perform() throws RepositoryException {
NodeDelegate parent = property.getParent();
if (parent == null) {
throw new AccessDeniedException();
} else {
return NodeImpl.createNode(parent, sessionContext);
}
}
});
}
@Override
public boolean isNew() {
return sessionDelegate.safePerform(new PropertyOperation<Boolean>(dlg, "isNew") {
@NotNull
@Override
public Boolean perform() {
return property.getStatus() == Status.NEW;
}
});
}
@Override
public boolean isModified() {
return sessionDelegate.safePerform(new PropertyOperation<Boolean>(dlg, "isModified") {
@NotNull
@Override
public Boolean perform() {
return property.getStatus() == Status.MODIFIED;
}
});
}
@Override
public void remove() throws RepositoryException {
sessionDelegate.performVoid(new ItemWriteOperation<Void>("remove") {
@Override
public void checkPreconditions() throws RepositoryException {
super.checkPreconditions();
if (!getParent().isCheckedOut() && getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) {
throw new VersionException(
"Cannot set property. Node is checked in.");
}
}
@Override
public void performVoid() {
dlg.remove();
}
@Override
public String toString() {
return String.format("Removing property [%s/%s] ", dlg.getPath(), dlg.getName());
}
});
}
@Override
public void accept(ItemVisitor visitor) throws RepositoryException {
visitor.visit(this);
}
//-----------------------------------------------------------< Property >---
@Override
public void setValue(Value value) throws RepositoryException {
if (value == null) {
remove();
} else {
internalSetValue(value);
}
}
@Override
public void setValue(final Value[] values) throws RepositoryException {
if (values == null) {
remove();
} else {
internalSetValue(values);
}
}
@Override
public void setValue(String value) throws RepositoryException {
if (value == null) {
remove();
} else {
setValue(getValueFactory().createValue(value));
}
}
@Override
public void setValue(String[] strings) throws RepositoryException {
if (strings == null) {
remove();
} else {
ValueFactory factory = getValueFactory();
Value[] values = new Value[strings.length];
for (int i = 0; i < strings.length; i++) {
if (strings[i] != null) {
values[i] = factory.createValue(strings[i]);
}
}
internalSetValue(values);
}
}
@Override @SuppressWarnings("deprecation")
public void setValue(InputStream value) throws RepositoryException {
if (value == null) {
remove();
} else {
setValue(getValueFactory().createValue(value));
}
}
@Override
public void setValue(Binary value) throws RepositoryException {
if (value == null) {
remove();
} else {
setValue(getValueFactory().createValue(value));
}
}
@Override
public void setValue(long value) throws RepositoryException {
setValue(getValueFactory().createValue(value));
}
@Override
public void setValue(double value) throws RepositoryException {
setValue(getValueFactory().createValue(value));
}
@Override
public void setValue(BigDecimal value) throws RepositoryException {
if (value == null) {
remove();
} else {
setValue(getValueFactory().createValue(value));
}
}
@Override
public void setValue(Calendar value) throws RepositoryException {
if (value == null) {
remove();
} else {
setValue(getValueFactory().createValue(value));
}
}
@Override
public void setValue(boolean value) throws RepositoryException {
setValue(getValueFactory().createValue(value));
}
@Override
public void setValue(Node value) throws RepositoryException {
if (value == null) {
remove();
} else {
setValue(getValueFactory().createValue(value));
}
}
@Override
@NotNull
public Value getValue() throws RepositoryException {
return perform(new PropertyOperation<Value>(dlg, "getValue") {
@NotNull
@Override
public Value perform() throws RepositoryException {
return new PartialValueFactory(sessionContext, sessionContext.getBlobAccessProvider())
.createValue(property.getSingleState());
}
});
}
@Override
@NotNull
public Value[] getValues() throws RepositoryException {
return perform(new PropertyOperation<List<Value>>(dlg, "getValues") {
@NotNull
@Override
public List<Value> perform() throws RepositoryException {
return new PartialValueFactory(sessionContext, sessionContext.getBlobAccessProvider())
.createValues(property.getMultiState());
}
}).toArray(NO_VALUES);
}
@Override
@NotNull
public String getString() throws RepositoryException {
return getValue().getString();
}
@SuppressWarnings("deprecation")
@Override
@NotNull
public InputStream getStream() throws RepositoryException {
return getValue().getStream();
}
@Override
@NotNull
public Binary getBinary() throws RepositoryException {
return getValue().getBinary();
}
@Override
public long getLong() throws RepositoryException {
return getValue().getLong();
}
@Override
public double getDouble() throws RepositoryException {
return getValue().getDouble();
}
@Override
@NotNull
public BigDecimal getDecimal() throws RepositoryException {
return getValue().getDecimal();
}
@Override
@NotNull
public Calendar getDate() throws RepositoryException {
return getValue().getDate();
}
@Override
public boolean getBoolean() throws RepositoryException {
return getValue().getBoolean();
}
@Override
@NotNull
public Node getNode() throws RepositoryException {
return perform(new PropertyOperation<Node>(dlg, "getNode") {
@NotNull
@Override
public Node perform() throws RepositoryException {
// TODO: avoid nested calls
Value value = getValue();
switch (value.getType()) {
case PropertyType.REFERENCE:
case PropertyType.WEAKREFERENCE:
return getSession().getNodeByIdentifier(value.getString());
case PropertyType.PATH:
case PropertyType.NAME:
String path = value.getString();
if (path.startsWith("[") && path.endsWith("]")) {
// identifier path
String identifier = path.substring(1, path.length() - 1);
return getSession().getNodeByIdentifier(identifier);
}
else {
try {
return (path.charAt(0) == '/') ? getSession().getNode(path) : getParent().getNode(path);
} catch (PathNotFoundException e) {
throw new ItemNotFoundException(path);
}
}
case PropertyType.STRING:
try {
Value refValue = ValueHelper.convert(value, PropertyType.REFERENCE, getValueFactory());
return getSession().getNodeByIdentifier(refValue.getString());
} catch (ItemNotFoundException e) {
throw e;
} catch (RepositoryException e) {
// try if STRING value can be interpreted as PATH value
Value pathValue = ValueHelper.convert(value, PropertyType.PATH, getValueFactory());
path = pathValue.getString();
try {
return (path.charAt(0) == '/') ? getSession().getNode(path) : getParent().getNode(path);
} catch (PathNotFoundException e1) {
throw new ItemNotFoundException(pathValue.getString());
}
}
default:
throw new ValueFormatException("Property value cannot be converted to a PATH, REFERENCE or WEAKREFERENCE");
}
}
});
}
@Override
@NotNull
public Property getProperty() throws RepositoryException {
return perform(new PropertyOperation<Property>(dlg, "getProperty") {
@NotNull
@Override
public Property perform() throws RepositoryException {
// TODO: avoid nested calls
Value value = getValue();
Value pathValue = ValueHelper.convert(value, PropertyType.PATH, getValueFactory());
String path = pathValue.getString();
try {
return (path.charAt(0) == '/') ? getSession().getProperty(path) : getParent().getProperty(path);
} catch (PathNotFoundException e) {
throw new ItemNotFoundException(path);
}
}
});
}
@Override
public long getLength() throws RepositoryException {
return getLength(getValue());
}
@Override
@NotNull
public long[] getLengths() throws RepositoryException {
Value[] values = getValues();
long[] lengths = new long[values.length];
for (int i = 0; i < values.length; i++) {
lengths[i] = getLength(values[i]);
}
return lengths;
}
@Override
@NotNull
public PropertyDefinition getDefinition() throws RepositoryException {
return perform(new PropertyOperation<PropertyDefinition>(dlg, "getDefinition") {
@NotNull
@Override
public PropertyDefinition perform() throws RepositoryException {
return getNodeTypeManager().getDefinition(
property.getParent().getTree(),
property.getPropertyState(), true);
}
});
}
@Override
public int getType() throws RepositoryException {
return perform(new PropertyOperation<Integer>(dlg, "getType") {
@NotNull
@Override
public Integer perform() throws RepositoryException {
return property.getPropertyState().getType().tag();
}
});
}
@Override
public boolean isMultiple() throws RepositoryException {
return perform(new PropertyOperation<Boolean>(dlg, "isMultiple") {
@NotNull
@Override
public Boolean perform() throws RepositoryException {
return property.getPropertyState().isArray();
}
});
}
//------------------------------------------------------------< internal >---
/**
* Return the length of the specified JCR value object.
*
* @param value The value.
* @return The length of the given value.
* @throws RepositoryException If an error occurs.
*/
private static long getLength(Value value) throws RepositoryException {
if (value.getType() == PropertyType.BINARY) {
return value.getBinary().getSize();
} else {
return value.getString().length();
}
}
private void internalSetValue(@NotNull final Value value)
throws RepositoryException {
sessionDelegate.performVoid(new ItemWriteOperation<Void>("internalSetValue") {
@Override
public void checkPreconditions() throws RepositoryException {
super.checkPreconditions();
if (!getParent().isCheckedOut() && getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) {
throw new VersionException(
"Cannot set property. Node is checked in.");
}
}
@Override
public void performVoid() throws RepositoryException {
Type<?> type = dlg.getPropertyState().getType();
if (type.isArray()) {
throw new ValueFormatException(
"This is a multi-valued property");
}
Value converted = ValueHelper.convert(
value, type.tag(), getValueFactory());
dlg.setState(createSingleState(dlg.getName(), converted, type));
}
@Override
public String toString() {
return String.format("Setting property [%s/%s]", dlg.getPath(), dlg.getName());
}
});
}
private void internalSetValue(@NotNull final Value[] values)
throws RepositoryException {
if (values.length > MV_PROPERTY_WARN_THRESHOLD) {
LOG.warn("Large multi valued property [{}] detected ({} values).",dlg.getPath(), values.length);
}
sessionDelegate.performVoid(new ItemWriteOperation<Void>("internalSetValue") {
@Override
public void checkPreconditions() throws RepositoryException {
super.checkPreconditions();
if (!getParent().isCheckedOut() && getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) {
throw new VersionException(
"Cannot set property. Node is checked in.");
}
}
@Override
public void performVoid() throws RepositoryException {
Type<?> type = dlg.getPropertyState().getType();
if (!type.isArray()) {
throw new ValueFormatException(
"This is a single-valued property");
}
List<Value> converted = newArrayListWithCapacity(values.length);
ValueFactory factory = getValueFactory();
for (Value value : values) {
if (value != null) {
converted.add(ValueHelper.convert(
value, type.tag(), factory));
}
}
dlg.setState(createMultiState(dlg.getName(), converted, type));
}
@Override
public String toString() {
return String.format("Setting property [%s/%s]", dlg.getPath(), dlg.getName());
}
});
}
}