All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.vault.util.DocViewProperty Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * 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.vault.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.jcr.Binary;
import javax.jcr.InvalidSerializedDataException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.VersionException;

import org.apache.jackrabbit.api.ReferenceBinary;
import org.apache.jackrabbit.commons.jackrabbit.SimpleReferenceBinary;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.util.XMLChar;
import org.apache.jackrabbit.value.ValueHelper;
import org.jetbrains.annotations.NotNull;

/**
 * Helper class that represents a JCR property in the FileVault (enhanced) document view format.
 * It contains formatting and parsing methods for writing/reading enhanced
 * docview properties.
 * 
* The string representation adheres to the following grammar: *
 * prop:= [ "{" type "}" ] ( value | "[" [ value { "," value } ] "]" )
 * type := {@link PropertyType#nameFromValue(int)} | {@link #BINARY_REF}
 * value := is a string representation of the value where the following characters are escaped: ',\[{' with a leading '\'
 * 
 * 
* @deprecated Use {@link DocViewProperty2} instead. */ @Deprecated public class DocViewProperty { public static final String BINARY_REF = "BinaryRef"; /** * name of the property */ public final String name; /** * value(s) of the property. always contains at least one value if this is * not a mv property. */ public final String[] values; /** * indicates a multi-value property */ public final boolean isMulti; /** * type of this property (can be undefined) */ public final int type; /** * indicates a binary ref property */ public final boolean isReferenceProperty; /** * set of unambigous property names (which never need an explicit type descriptor as the types are defined by the spec) */ private static final Set UNAMBIGOUS = new HashSet<>(); static { UNAMBIGOUS.add("jcr:primaryType"); UNAMBIGOUS.add("jcr:mixinTypes"); } /** * Creates a new property based on an array of {@link Value}s * @param name the name of the property * @param values the values (always an array, may be empty), must not contain {@code null} items * @param type the type of the property * @param isMulti {@code true} in case this is a multivalue property * @param sort {@code true} in case the value array should be sorted first * @param useBinaryReferences to use the binary reference as value (if available) * @return the new property * @throws RepositoryException */ public static DocViewProperty fromValues(@NotNull String name, @NotNull Value[] values, int type, boolean isMulti, boolean sort, boolean useBinaryReferences) throws RepositoryException { List strValues = new ArrayList<>(); if (isMulti) { if (sort) { Arrays.sort(values, ValueComparator.getInstance()); } } for (Value value : values) { strValues.add(serializeValue(value, useBinaryReferences)); } Boolean isBinaryRef = null; if (type == PropertyType.BINARY) { // either only binary references or regular binaries for (String strValue : strValues) { boolean isCurrentValueBinaryRef = !strValue.isEmpty(); if (isBinaryRef == null) { isBinaryRef = isCurrentValueBinaryRef; } else { if (isBinaryRef != isCurrentValueBinaryRef) { throw new ValueFormatException("Mixed binary references and regular binary values in the same multi-value property is not supported"); } } } } if (isBinaryRef == null) { isBinaryRef = false; } return new DocViewProperty(name, strValues.toArray(new String[0]), isMulti, type, isBinaryRef); } /** * Creates a new property based on a JCR {@link Property} object * @param prop the JCR property * @param sort if {@code true} multi-value properties should be sorted * @param useBinaryReferences {@code true} to use binary references * @return the new property * @throws IllegalArgumentException if single value property and not exactly 1 value is given. * @throws RepositoryException if another error occurs */ public static DocViewProperty fromProperty(@NotNull Property prop, boolean sort, boolean useBinaryReferences) throws RepositoryException { boolean isMultiValue = prop.getDefinition().isMultiple(); final Value[] values; if (isMultiValue) { values = prop.getValues(); } else { values = new Value[] { prop.getValue() }; } return fromValues(prop.getName(), values, prop.getType(), isMultiValue, sort, useBinaryReferences); } public static DocViewProperty fromDocViewProperty2(DocViewProperty2 property) { return new DocViewProperty(property.getName().toString(), property.getStringValues().toArray(new String[0]), property.isMultiValue(), property.getType(), property.isReferenceProperty()); } static String serializeValue(Value value, boolean useBinaryReferences) throws RepositoryException { // special handling for binaries String strValue = null; if (value.getType() == PropertyType.BINARY) { if (useBinaryReferences) { Binary bin = value.getBinary(); if (bin instanceof ReferenceBinary) { strValue = ((ReferenceBinary) bin).getReference(); } } if (strValue == null) { // leave value empty for non reference binaries or where reference is null strValue = ""; } } else { strValue = ValueHelper.serialize(value, false); } return strValue; } /** * Creates a new property. * @param name name of the property * @param values values. * @param multi multiple flag * @param type type of the property * @throws IllegalArgumentException if single value property and not exactly 1 value is given. */ public DocViewProperty(String name, String[] values, boolean multi, int type) { this(name, values, multi, type, false); } /** * Creates a new property. * @param name name of the property * @param values string representation of values. * @param multi indicates if this is a multi-value property * @param type type of the property * @param isRef {@code true} to indicate that this is a binary reference property * @throws IllegalArgumentException if single value property and not exactly 1 value is given. */ public DocViewProperty(String name, String[] values, boolean multi, int type, boolean isRef) { this.name = name; this.values = values; isMulti = multi; // validate type if (type == PropertyType.UNDEFINED) { if ("jcr:primaryType".equals(name) || "jcr:mixinTypes".equals(name)) { type = PropertyType.NAME; } } this.type = type; if (!isMulti && values.length != 1) { throw new IllegalArgumentException("Single value property needs exactly 1 value."); } this.isReferenceProperty = isRef; } /** * Parses a enhanced docview property string and returns the property. * @param name name of the property * @param value (attribute) value * @return a property */ public static DocViewProperty parse(String name, String value) { boolean isMulti = false; boolean isBinaryRef = false; int type = PropertyType.UNDEFINED; int pos = 0; char state = 'b'; List vals = null; StringBuilder tmp = new StringBuilder(); int unicode = 0; int unicodePos = 0; while (pos < value.length()) { char c = value.charAt(pos++); switch (state) { case 'b': // begin (type or array or value) if (c == '{') { state = 't'; } else if (c == '[') { isMulti = true; state = 'v'; } else if (c == '\\') { state = 'e'; } else { tmp.append(c); state = 'v'; } break; case 'a': // array (array or value) if (c == '[') { isMulti = true; state = 'v'; } else if (c == '\\') { state = 'e'; } else { tmp.append(c); state = 'v'; } break; case 't': if (c == '}') { if (BINARY_REF.equals(tmp.toString())) { type = PropertyType.BINARY; isBinaryRef = true; } else { type = PropertyType.valueFromName(tmp.toString()); } tmp.setLength(0); state = 'a'; } else { tmp.append(c); } break; case 'v': // value if (c == '\\') { state = 'e'; } else if (c == ',' && isMulti) { if (vals == null) { vals = new LinkedList(); } vals.add(tmp.toString()); tmp.setLength(0); } else if (c == ']' && isMulti && pos == value.length()) { if (tmp.length() > 0 || vals != null) { if (vals == null) { vals = new LinkedList(); } vals.add(tmp.toString()); tmp.setLength(0); } } else { tmp.append(c); } break; case 'e': // escaped if (c == 'u') { state = 'u'; unicode = 0; unicodePos = 0; } else if (c == '0') { // special case to treat empty values. see JCR-3661 state = 'v'; if (vals == null) { vals = new LinkedList(); } } else { state = 'v'; tmp.append(c); } break; case 'u': // unicode escaped unicode = (unicode << 4) + Character.digit(c, 16); if (++unicodePos == 4) { tmp.appendCodePoint(unicode); state = 'v'; } break; } } String[] values; if (isMulti) { // add value if missing ']' if (tmp.length() > 0) { if (vals == null) { vals = new LinkedList(); } vals.add(tmp.toString()); } if (vals == null) { values = Constants.EMPTY_STRING_ARRAY; } else { values = vals.toArray(new String[vals.size()]); } } else { values = new String[]{tmp.toString()}; } return new DocViewProperty(name, values, isMulti, type, isBinaryRef); } /** * Formats (serializes) the given JCR property value according to the enhanced docview syntax. * @param prop the JCR property * @return the formatted string of the property value * @throws RepositoryException if a repository error occurs */ public static String format(Property prop) throws RepositoryException { return format(prop, false, false); } /** * Formats (serializes) the given JCR property value to the enhanced docview syntax. * @param prop the JCR property * @param sort if {@code true} multi-value properties are sorted * @param useBinaryReferences {@code true} to use binary references * @return the formatted string of the property value * @throws RepositoryException if a repository error occurs */ public static String format(Property prop, boolean sort, boolean useBinaryReferences) throws RepositoryException { return fromProperty(prop, sort, useBinaryReferences).formatValue(); } /** * Generates string representation of this DocView property value. * @return the string representation of the value */ public String formatValue() { StringBuilder attrValue = new StringBuilder(); if (isAmbiguous(type, name)) { final String strType; if (isReferenceProperty) { strType = BINARY_REF; } else { strType = PropertyType.nameFromValue(type); } attrValue.append('{').append(strType).append('}'); } if (isMulti) { attrValue.append('['); } for (int i=0;i 0) { attrValue.append(','); } switch (type) { case PropertyType.STRING: case PropertyType.NAME: case PropertyType.PATH: attrValue.append(escape(value, isMulti)); break; default: attrValue.append(value); } } } if (isMulti) { attrValue.append(']'); } return attrValue.toString(); } /** * Escapes the value * @param buf buffer to append to * @param value value to escape * @param isMulti indicates multi value property * @deprecated Rather use {@link #escape(String, boolean)} */ @Deprecated protected static void escape(StringBuffer buf, String value, boolean isMulti) { buf.append(escape(value, isMulti)); } /** * Escapes the value * @param value value to escape * @param isMulti indicates multi value property * @return the escaped value */ protected static String escape(String value, boolean isMulti) { StringBuilder buf = new StringBuilder(); for (int i=0; i> 12) & 15]); buf.append(Text.hexTable[(c >> 8) & 15]); buf.append(Text.hexTable[(c >> 4) & 15]); buf.append(Text.hexTable[c & 15]); } else { buf.append(c); } } return buf.toString(); } /** * Checks if the type of the given property is ambiguous in respect to it's * property definition. the current implementation just checks some well * known properties. * * @param prop the property * @return type * @throws RepositoryException if a repository error occurs * @deprecated was not supposed to be public but rather is an implementation detail, should not be called at all */ @Deprecated public static boolean isAmbiguous(Property prop) throws RepositoryException { return isAmbiguous(prop.getType(), prop.getName()); } /** * Checks if the type of the given property is ambiguous in respect to it's * property definition. the current implementation just checks some well * known properties. * * @param type the type * @param name the name * @return {@code true} if type information should be emitted, otherwise {@code false} */ private static boolean isAmbiguous(int type, String name) { return type != PropertyType.STRING && type != PropertyType.UNDEFINED && !UNAMBIGOUS.contains(name); } /** * Sets this property on the given node * * @param node the node * @return {@code true} if the value was modified. * @throws RepositoryException if a repository error occurs */ public boolean apply(Node node) throws RepositoryException { Property prop = node.hasProperty(name) ? node.getProperty(name) : null; // check if multiple flags are equal if (prop != null && isMulti != prop.getDefinition().isMultiple()) { prop.remove(); prop = null; } if (prop != null) { int propType = prop.getType(); if (propType != type && (propType != PropertyType.STRING || type != PropertyType.UNDEFINED)) { // never compare if types differ prop = null; } } if (isMulti) { Value[] vs = prop == null ? null : prop.getValues(); if (type == PropertyType.BINARY) { return applyBinary(node, vs); } if (vs != null && vs.length == values.length) { // quick check all values boolean modified = false; for (int i=0; i binaryValues = new ArrayList<>(values.length); if (!isReferenceProperty) { for (String value : values) { // empty string is used for binary properties which should not be touched! if (!value.isEmpty()) { throw new InvalidSerializedDataException("Inline binaries are only supported as binary references, but is " + value); } } // just silently ignore binaries with only empty string values return false; } try { boolean modified = false; for (int n=0; n < values.length; n++) { String value = values[n]; ReferenceBinary ref = new SimpleReferenceBinary(value); Value binaryValue = node.getSession().getValueFactory().createValue(ref); binaryValues.add(binaryValue); // compare with existing value if (modified == false && existingValues != null && n < existingValues.length && existingValues[n] != null) { Binary existingBinary = existingValues[0].getBinary(); if (!existingBinary.equals(binaryValue.getBinary())) { modified = true; } } else { modified = true; } } if (!modified) { return false; } if (isMulti) { node.setProperty(name, binaryValues.toArray(new Value[0])); } else { node.setProperty(name, binaryValues.get(0)); } // the binary property is always modified (TODO: check if still correct with JCRVLT-110) return true; } finally { for (Value value : binaryValues) { value.getBinary().dispose(); } } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (isMulti ? 1231 : 1237); result = prime * result + (isReferenceProperty ? 1231 : 1237); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + type; result = prime * result + Arrays.hashCode(values); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DocViewProperty other = (DocViewProperty) obj; if (isMulti != other.isMulti) return false; if (isReferenceProperty != other.isReferenceProperty) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (type != other.type) return false; if (!Arrays.equals(values, other.values)) return false; return true; } /** * This does not return the string representation of the enhanced docview property value but rather a descriptive string including the property name for debugging purposes. * Use {@link #formatValue()}, {@link #format(Property)} or {@link #format(Property, boolean, boolean)} to get the enhanced docview string representation of the value. */ @Override public String toString() { return "DocViewProperty [name=" + name + ", values=" + Arrays.toString(values) + ", isMulti=" + isMulti + ", type=" + PropertyType.nameFromValue(type) + ", isReferenceProperty=" + isReferenceProperty + "]"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy