blob: 82c7736fa91b998a413aadf01453ac4ae38da7d4 [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.plugins.value.jcr;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.util.Calendar;
import javax.jcr.Binary;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import com.google.common.base.Objects;
import org.apache.jackrabbit.api.JackrabbitValue;
import org.apache.jackrabbit.api.binary.BinaryDownloadOptions;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.IllegalRepositoryStateException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.api.blob.BlobAccessProvider;
import org.apache.jackrabbit.oak.api.blob.BlobDownloadOptions;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.plugins.value.Conversions;
import org.apache.jackrabbit.oak.plugins.value.ErrorValue;
import org.apache.jackrabbit.oak.plugins.value.OakValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of {@link Value} based on {@code PropertyState}.
*/
class ValueImpl implements JackrabbitValue, OakValue {
private static final Logger LOG = LoggerFactory.getLogger(ValueImpl.class);
private final PropertyState propertyState;
private final Type<?> type;
private final int index;
private final NamePathMapper namePathMapper;
private final BlobAccessProvider blobAccessProvider;
private InputStream stream = null;
/**
* Create a new {@code Value} instance
* @param property The property state this instance is based on
* @param index The index
* @param namePathMapper The name/path mapping used for converting JCR names/paths to
* the internal representation.
* @param blobAccessProvider The blob access provider
* @throws IllegalArgumentException if {@code index < propertyState.count()}
* @throws RepositoryException if the underlying node state cannot be accessed
*/
private ValueImpl(@NotNull PropertyState property, int index,
@NotNull NamePathMapper namePathMapper,
@NotNull BlobAccessProvider blobAccessProvider)
throws RepositoryException {
checkArgument(index < property.count());
this.propertyState = checkNotNull(property);
this.type = getType(property);
this.index = index;
this.namePathMapper = checkNotNull(namePathMapper);
this.blobAccessProvider = checkNotNull(blobAccessProvider);
}
/**
* Create a new {@code Value} instance
* @param property The property state this instance is based on
* @param namePathMapper The name/path mapping used for converting JCR names/paths to
* the internal representation.
* @param blobAccessProvider The blob access provider
* @throws IllegalArgumentException if {@code property.isArray()} is {@code true}.
* @throws RepositoryException if the underlying node state cannot be accessed
*/
ValueImpl(@NotNull PropertyState property,
@NotNull NamePathMapper namePathMapper,
@NotNull BlobAccessProvider blobAccessProvider)
throws RepositoryException {
this(checkSingleValued(property), 0, namePathMapper, checkNotNull(blobAccessProvider));
}
private static PropertyState checkSingleValued(PropertyState property) {
checkArgument(!property.isArray());
return property;
}
/**
* Create a new {@code Value} instance
* @param property The property state this instance is based on
* @param index The index
* @param namePathMapper The name/path mapping used for converting JCR names/paths to
* the internal representation.
* @param blobAccessProvider The blob access provider
* @throws IllegalArgumentException if {@code index < propertyState.count()}
*/
@NotNull
static Value newValue(@NotNull PropertyState property, int index,
@NotNull NamePathMapper namePathMapper,
@NotNull BlobAccessProvider blobAccessProvider) {
try {
return new ValueImpl(property, index, namePathMapper, blobAccessProvider);
} catch (RepositoryException e) {
return new ErrorValue(e);
}
}
/**
* Create a new {@code Value} instance
* @param property The property state this instance is based on
* @param namePathMapper The name/path mapping used for converting JCR names/paths to
* the internal representation.
* @param blobAccessProvider The blob access provider
* @throws IllegalArgumentException if {@code property.isArray()} is {@code true}.
*/
@NotNull
static Value newValue(@NotNull PropertyState property,
@NotNull NamePathMapper namePathMapper,
@NotNull BlobAccessProvider blobAccessProvider) {
try {
return new ValueImpl(property, 0, namePathMapper, blobAccessProvider);
} catch (RepositoryException e) {
return new ErrorValue(e);
}
}
//-----------------------------------------------------------< OakValue >---
public Blob getBlob() throws RepositoryException {
return getValue(Type.BINARY, index);
}
/**
* Same as {@link #getString()} unless that names and paths are returned in their
* Oak representation instead of being mapped to their JCR representation.
* @return A String representation of the value of this property.
*/
public String getOakString() throws RepositoryException {
return getValue(Type.STRING, index);
}
//--------------------------------------------------------------< Value >---
/**
* @see javax.jcr.Value#getType()
*/
@Override
public int getType() {
return type.tag();
}
/**
* @see javax.jcr.Value#getBoolean()
*/
@Override
public boolean getBoolean() throws RepositoryException {
switch (getType()) {
case PropertyType.STRING:
case PropertyType.BINARY:
case PropertyType.BOOLEAN:
return getValue(Type.BOOLEAN, index);
default:
throw new ValueFormatException("Incompatible type " + PropertyType.nameFromValue(getType()));
}
}
/**
* @see javax.jcr.Value#getDate()
*/
@Override
public Calendar getDate() throws RepositoryException {
try {
switch (getType()) {
case PropertyType.STRING:
case PropertyType.BINARY:
case PropertyType.DATE:
String value = getValue(Type.DATE, index);
return Conversions.convert(value).toCalendar();
case PropertyType.LONG:
case PropertyType.DOUBLE:
case PropertyType.DECIMAL:
return Conversions.convert(getValue(Type.LONG, index)).toCalendar();
default:
throw new ValueFormatException("Incompatible type " + PropertyType.nameFromValue(getType()));
}
}
catch (IllegalArgumentException e) {
throw new ValueFormatException("Error converting value to date", e);
}
}
/**
* @see javax.jcr.Value#getDecimal()
*/
@Override
public BigDecimal getDecimal() throws RepositoryException {
try {
switch (getType()) {
case PropertyType.STRING:
case PropertyType.BINARY:
case PropertyType.LONG:
case PropertyType.DOUBLE:
case PropertyType.DATE:
case PropertyType.DECIMAL:
return getValue(Type.DECIMAL, index);
default:
throw new ValueFormatException("Incompatible type " + PropertyType.nameFromValue(getType()));
}
}
catch (IllegalArgumentException e) {
throw new ValueFormatException("Error converting value to decimal", e);
}
}
/**
* @see javax.jcr.Value#getDouble()
*/
@Override
public double getDouble() throws RepositoryException {
try {
switch (getType()) {
case PropertyType.STRING:
case PropertyType.BINARY:
case PropertyType.LONG:
case PropertyType.DOUBLE:
case PropertyType.DATE:
case PropertyType.DECIMAL:
return getValue(Type.DOUBLE, index);
default:
throw new ValueFormatException("Incompatible type " + PropertyType.nameFromValue(getType()));
}
}
catch (IllegalArgumentException e) {
throw new ValueFormatException("Error converting value to double", e);
}
}
/**
* @see javax.jcr.Value#getLong()
*/
@Override
public long getLong() throws RepositoryException {
try {
switch (getType()) {
case PropertyType.STRING:
case PropertyType.BINARY:
case PropertyType.LONG:
case PropertyType.DOUBLE:
case PropertyType.DATE:
case PropertyType.DECIMAL:
return getValue(Type.LONG, index);
default:
throw new ValueFormatException("Incompatible type " + PropertyType.nameFromValue(getType()));
}
}
catch (IllegalArgumentException e) {
throw new ValueFormatException("Error converting value to long", e);
}
}
/**
* @see javax.jcr.Value#getString()
*/
@Override
public String getString() throws RepositoryException {
checkState(getType() != PropertyType.BINARY || stream == null,
"getStream has previously been called on this Value instance. " +
"In this case a new Value instance must be acquired in order to successfully call this method.");
switch (getType()) {
case PropertyType.NAME:
return namePathMapper.getJcrName(getOakString());
case PropertyType.PATH:
String s = getOakString();
if (s.startsWith("[") && s.endsWith("]")) {
// identifier paths are returned as-is (JCR 2.0, 3.4.3.1)
return s;
} else {
return namePathMapper.getJcrPath(s);
}
default:
return getOakString();
}
}
/**
* @see javax.jcr.Value#getStream()
*/
@Override
public InputStream getStream() throws IllegalStateException, RepositoryException {
if (stream == null) {
stream = getBlob().getNewStream();
}
return stream;
}
/**
* @see javax.jcr.Value#getBinary()
*/
@Override
public Binary getBinary() throws RepositoryException {
return new BinaryImpl(this);
}
@Override
public String getContentIdentity() {
try {
return getBlob().getContentIdentity();
} catch (RepositoryException e) {
LOG.warn("Error getting content identity", e);
return null;
}
}
//-------------------------------------------------------------< Object >---
/**
* @see Object#equals(Object)
*/
@Override
public boolean equals(Object other) {
if (other instanceof ValueImpl) {
ValueImpl that = (ValueImpl) other;
Type<?> thisType = this.type;
if (thisType.isArray()) {
thisType = thisType.getBaseType();
}
Type<?> thatType = that.type;
if (thatType.isArray()) {
thatType = thatType.getBaseType();
}
try {
return thisType == thatType
&& Objects.equal(
getValue(thatType, index),
that.getValue(thatType, that.index));
} catch (RepositoryException e) {
LOG.warn("Error while comparing values", e);
return false;
}
} else {
return false;
}
}
/**
* @see Object#hashCode()
*/
@Override
public int hashCode() {
try {
if (getType() == PropertyType.BINARY) {
return getValue(Type.BINARY, index).hashCode();
} else {
return getValue(Type.STRING, index).hashCode();
}
} catch (RepositoryException e) {
LOG.warn("Error while calculating hash code", e);
return 0;
}
}
@Override
public String toString() {
try {
return getValue(Type.STRING, index);
} catch (RepositoryException e) {
return e.toString();
}
}
@Nullable
URI getDownloadURI(@NotNull Blob blob, @NotNull BinaryDownloadOptions downloadOptions) {
if (blobAccessProvider == null) {
return null;
} else {
return blobAccessProvider.getDownloadURI(blob,
new BlobDownloadOptions(
downloadOptions.getMediaType(),
downloadOptions.getCharacterEncoding(),
downloadOptions.getFileName(),
downloadOptions.getDispositionType())
);
}
}
//------------------------------------------------------------< private >---
private <T> T getValue(Type<T> type, int index) throws RepositoryException {
try {
return propertyState.getValue(type, index);
} catch (IllegalRepositoryStateException e) {
throw new RepositoryException(e);
}
}
private static Type<?> getType(PropertyState property) throws RepositoryException {
try {
return property.getType();
} catch (IllegalRepositoryStateException e) {
throw new RepositoryException(e);
}
}
}