package com.thinkbiganalytics.metadata.modeshape.support;
/*-
* #%L
* thinkbig-metadata-modeshape
* %%
* Copyright (C) 2017 ThinkBig Analytics
* %%
* Licensed 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.
* #L%
*/
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.thinkbiganalytics.classnameregistry.ClassNameChangeRegistry;
import com.thinkbiganalytics.metadata.api.MissingUserPropertyException;
import com.thinkbiganalytics.metadata.api.extension.ExtensibleType;
import com.thinkbiganalytics.metadata.api.extension.ExtensibleTypeBuilder;
import com.thinkbiganalytics.metadata.api.extension.ExtensibleTypeProvider;
import com.thinkbiganalytics.metadata.api.extension.FieldDescriptor;
import com.thinkbiganalytics.metadata.api.extension.FieldDescriptorBuilder;
import com.thinkbiganalytics.metadata.api.extension.UserFieldDescriptor;
import com.thinkbiganalytics.metadata.modeshape.JcrMetadataAccess;
import com.thinkbiganalytics.metadata.modeshape.MetadataRepositoryException;
import com.thinkbiganalytics.metadata.modeshape.UnknownPropertyException;
import com.thinkbiganalytics.metadata.modeshape.common.JcrObject;
import com.thinkbiganalytics.metadata.modeshape.extension.JcrExtensiblePropertyCollection;
import com.thinkbiganalytics.metadata.modeshape.extension.JcrUserFieldDescriptor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.joda.time.DateTime;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.Nonnull;
import javax.jcr.AccessDeniedException;
import javax.jcr.Binary;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
/**
* Utility functions for JCR properties.
*/
public class JcrPropertyUtil {
/**
* Encoding for user-defined property names
*/
public static final String USER_PROPERTY_ENCODING = "UTF-8";
protected static final ObjectWriter writer;
protected static final ObjectReader reader;
static {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
reader = mapper.reader();
writer = mapper.writer();
}
/**
* Instances of {@code JcrPropertyUtil} may not be constructed.
*
* @throws UnsupportedOperationException always
*/
private JcrPropertyUtil() {
throw new UnsupportedOperationException();
}
public static String getName(Node node) {
try {
return node.getName();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access name property of node: " + node, e);
}
}
public static String getName(Property prop) {
try {
return prop.getName();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get the name of property: " + prop, e);
}
}
public static String getString(Node node, String name) {
try {
Property prop = node.getProperty(name);
return prop.getString();
} catch (PathNotFoundException e) {
return null;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property: " + name, e);
}
}
/**
* Returns the boolean value of the property. if the property is a String type it will try to determine the boolean value. If it cannot get a boolean value, it will return false.
*/
public static boolean getBoolean(Node node, String name) {
return getBoolean(node, name, false);
}
private static boolean getBoolean(Node node, String name, boolean notFoundValue) {
try {
Property prop = node.getProperty(name);
if (PropertyType.STRING == prop.getType()) {
return BooleanUtils.toBoolean(prop.getString());
} else if (PropertyType.BOOLEAN == prop.getType()) {
return prop.getBoolean();
}
return notFoundValue;
} catch (PathNotFoundException e) {
return notFoundValue;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property: " + name, e);
}
}
public static boolean getBooleanOrDefault(Node node, String name, boolean notFoundValue) {
boolean val = notFoundValue;
try {
val = getBoolean(node, name);
} catch (Exception e) {
//swallow exception and return the default value
}
return val;
}
public static Long getLong(Node node, String name) {
try {
Property prop = node.getProperty(name);
return prop.getLong();
} catch (PathNotFoundException e) {
return null;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property: " + name, e);
}
}
public static <E extends Enum<E>> E getEnum(Node node, String name, Class<E> enumType, E defaultValue) {
try {
Property prop = node.getProperty(name);
return Enum.valueOf(enumType, prop.getString());
} catch (PathNotFoundException e) {
return defaultValue;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property: " + name, e);
}
}
private static <T> T readJsonValue(String name, Class<T> type, String json) {
try {
return reader.forType(type).readValue(json);
} catch (IOException e) {
if (ExceptionUtils.getRootCause(e) instanceof ClassNotFoundException) {
//attempt to find the old class name and replace it with the new one
ClassNotFoundException classNotFoundException = (ClassNotFoundException) ExceptionUtils.getRootCause(e);
String msg = classNotFoundException.getMessage();
msg = StringUtils.remove(msg, "java.lang.ClassNotFound:");
String oldName = StringUtils.trim(msg);
try {
Class newName = ClassNameChangeRegistry.findClass(oldName);
String newNameString = newName.getName();
if (StringUtils.contains(json, oldName)) {
//replace and try again
json = StringUtils.replace(json, oldName, newNameString);
return readJsonValue(name, type, json);
}
} catch (ClassNotFoundException c) {
}
}
throw new MetadataRepositoryException("Failed to deserialize JSON property: " + name, e);
}
}
public static <T> T getJsonObject(Node node, String name, Class<T> type) {
String json = getString(node, name);
return readJsonValue(name, type, json);
}
public static <T> void setJsonObject(Node node, String name, Object value) {
try {
String json = writer.forType(value.getClass()).writeValueAsString(value);
setProperty(node, name, json);
} catch (IOException e) {
throw new MetadataRepositoryException("Failed to serialize JSON property: " + value, e);
}
}
public static Optional<Property> findProperty(Node node, String name) {
try {
if (node.hasProperty(name)) {
return Optional.of(node.getProperty(name));
} else {
return Optional.empty();
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed attempting to locate a property: " + name, e);
}
}
public static <T> T getProperty(Node node, String name) {
return getProperty(node, name, false);
}
public static <T> T getProperty(Node node, String name, boolean allowNotFound) {
try {
Property prop = node.getProperty(name);
return asValue(prop);
} catch (PathNotFoundException e) {
if (allowNotFound) {
return null;
} else {
throw new UnknownPropertyException(name, e);
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property: " + name, e);
}
}
public static Map<String, Object> getProperties(Node node) {
try {
Map<String, Object> propMap = new HashMap<>();
PropertyIterator itr = node.getProperties();
while (itr.hasNext()) {
Property prop = (Property) itr.next();
propMap.put(prop.getName(), asValue(prop));
}
return propMap;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access properties", e);
}
}
public static Node setProperties(Session session, Node entNode, Map<String, Object> props) {
ValueFactory factory;
try {
factory = session.getValueFactory();
if (props != null) {
JcrMetadataAccess.ensureCheckoutNode(entNode);
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getValue() instanceof JcrExtensiblePropertyCollection) {
JcrExtensiblePropertyCollection propertyCollection = ((JcrExtensiblePropertyCollection) entry.getValue());
propertyCollection.getCollectionType();
Value[] values = new Value[propertyCollection.getCollection().size()];
int i = 0;
for (Object o : propertyCollection.getCollection()) {
boolean weak = false;
if (propertyCollection.getCollectionType() == PropertyType.WEAKREFERENCE) {
weak = true;
}
Value value = createValue(session, o, weak);
values[i] = value;
i++;
}
entNode.setProperty(entry.getKey(), values);
} else {
Value value = asValue(factory, entry.getValue());
entNode.setProperty(entry.getKey(), value);
}
}
}
return entNode;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to set properties", e);
}
}
public static boolean hasProperty(NodeType type, String propName) {
for (PropertyDefinition propDef : type.getPropertyDefinitions()) {
if (propDef.getName().equals(propName)) {
return true;
}
}
return false;
}
//
// public static <T> T asValue(Property prop) {
// return asValue(prop, null);
// }
@SuppressWarnings("unchecked")
public static <T> T asValue(Value value, Session session) throws AccessDeniedException {
try {
switch (value.getType()) {
case PropertyType.DECIMAL:
return (T) value.getDecimal();
case PropertyType.STRING:
return (T) value.getString();
case PropertyType.DOUBLE:
return (T) Double.valueOf(value.getDouble());
case PropertyType.LONG:
return (T) Long.valueOf(value.getLong());
case PropertyType.BOOLEAN:
return (T) Boolean.valueOf(value.getBoolean());
case PropertyType.DATE:
return (T) new DateTime(value.getDate().getTime());
case PropertyType.BINARY:
return (T) IOUtils.toByteArray(value.getBinary().getStream());
case PropertyType.REFERENCE:
String ref = value.getString();
return (T) session.getNodeByIdentifier(ref);
case PropertyType.WEAKREFERENCE:
String wref = value.getString();
try {
return (T) session.getNodeByIdentifier(wref);
} catch (ItemNotFoundException e) {
return null;
}
default:
return (T) value.getString();
}
} catch (AccessDeniedException e) {
throw e;
} catch (RepositoryException | IOException e) {
throw new MetadataRepositoryException("Failed to access property type", e);
}
}
public static String toString(Property prop) {
try {
return prop.getString();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get string value of property: " + prop, e);
}
}
@SuppressWarnings("unchecked")
public static <T> T asValue(Property prop) {
// STRING, BOOLEAN, LONG, DOUBLE, PATH, ENTITY
try {
int code = prop.getType();
if (prop.isMultiple()) {
List<T> list = new ArrayList<>();
Value[] values = prop.getValues();
if (values != null) {
for (Value value : values) {
try {
T o = asValue(value, prop.getSession());
list.add(o);
} catch (AccessDeniedException e) {
// We are not allowd to see the value (likely a node reference) then
// just ignore this value in the result list.
}
}
}
if (list.size() > 0) {
return (T) list;
} else {
return (T) Collections.emptyList();
}
} else {
if (code == PropertyType.BOOLEAN) {
return (T) Boolean.valueOf(prop.getBoolean());
} else if (code == PropertyType.STRING) {
return (T) prop.getString();
} else if (code == PropertyType.LONG) {
return (T) Long.valueOf(prop.getLong());
} else if (code == PropertyType.DOUBLE) {
return (T) Double.valueOf(prop.getDouble());
} else if (code == PropertyType.PATH) {
return (T) prop.getPath();
} else if (code == PropertyType.REFERENCE || code == PropertyType.WEAKREFERENCE) {
try {
return (T) prop.getNode();
} catch (AccessDeniedException e) {
// We are not allowd to see the referenced node so return null;
return null;
}
} else {
return (T) asValue(prop.getValue(), prop.getSession());
}
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property type", e);
}
}
public static Node lookupNodeReference(String nodeIdentifier, Session session) {
Node n = null;
if (session != null) {
try {
n = session.getNodeByIdentifier(nodeIdentifier);
} catch (RepositoryException e) {
}
}
return n;
}
public static void setWeakReferenceProperty(Node node, String name, Node ref) {
try {
//ensure checked out
JcrMetadataAccess.ensureCheckoutNode(node);
if (node == null) {
throw new IllegalArgumentException("Cannot set a property on a null-node!");
}
if (name == null) {
throw new IllegalArgumentException("Cannot set a property without a provided name");
}
Value weakRef = node.getSession().getValueFactory().createValue(ref, true);
node.setProperty(name, weakRef);
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to set weak ref property value: " + name + "=" + ref, e);
}
}
public static void setProperty(Node node, String name, Object value) {
try {
//ensure checked out
JcrMetadataAccess.ensureCheckoutNode(node);
if (node == null) {
throw new IllegalArgumentException("Cannot set a property on a null-node!");
}
if (name == null) {
throw new IllegalArgumentException("Cannot set a property without a provided name");
}
if (value == null) {
node.setProperty(name, (Value) null);
} else if (value instanceof Enum) {
node.setProperty(name, ((Enum) value).name());
} else if (value instanceof JcrObject) {
node.setProperty(name, ((JcrObject) value).getNode());
} else if (value instanceof Value) {
node.setProperty(name, (Value) value);
} else if (value instanceof Node) {
node.setProperty(name, (Node) value);
} else if (value instanceof Binary) {
node.setProperty(name, (Binary) value);
} else if (value instanceof Calendar) {
node.setProperty(name, (Calendar) value);
} else if (value instanceof DateTime) {
Calendar cal = Calendar.getInstance();
cal.setTime(((DateTime) value).toDate());
node.setProperty(name, cal);
} else if (value instanceof Date) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) value);
node.setProperty(name, cal);
} else if (value instanceof BigDecimal) {
node.setProperty(name, (BigDecimal) value);
} else if (value instanceof Long) {
node.setProperty(name, ((Long) value).longValue());
} else if (value instanceof Double) {
node.setProperty(name, (Double) value);
} else if (value instanceof Boolean) {
node.setProperty(name, (Boolean) value);
} else if (value instanceof InputStream) {
node.setProperty(name, (InputStream) value);
} else if (value instanceof Collection) {
String[] list = new String[((Collection<Object>) value).size()];
int pos = 0;
for (Object cal : (Collection<Object>) value) {
list[pos] = cal.toString();
pos += 1;
}
node.setProperty(name, list);
} else {
node.setProperty(name, value.toString());
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to set property value: " + name + "=" + value, e);
}
}
/**
* Used to retrieve the referenced nodes from a multi-valued property of type (WEAK)REFERENCE
*/
public static Set<Node> getReferencedNodeSet(Node node, String propName) {
try {
if (node == null) {
throw new IllegalArgumentException("Cannot set a property on a null-node!");
}
if (propName == null) {
throw new IllegalArgumentException("Cannot set a property without a provided name");
}
final Session session = node.getSession();
JcrMetadataAccess.ensureCheckoutNode(node);
if (node.hasProperty(propName)) {
return Arrays.stream(node.getProperty(propName).getValues())
.map(v -> {
try {
return (Node) JcrPropertyUtil.asValue(v, session);
} catch (AccessDeniedException e) {
// Not allowed to see the referenced node so return null.
return null;
}
})
.filter(n -> n != null) // weak refs can produce null nodes
.collect(Collectors.toSet());
} else {
return new HashSet<>();
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get the node property set: " + propName, e);
}
}
public static boolean addToSetProperty(Node node, String name, Object value) {
return addToSetProperty(node, name, value, false);
}
public static boolean addToSetProperty(Node node, String name, Object value, boolean weakReference) {
try {
JcrMetadataAccess.ensureCheckoutNode(node);
if (node == null) {
throw new IllegalArgumentException("Cannot set a property on a null-node!");
}
if (name == null) {
throw new IllegalArgumentException("Cannot set a property without a provided name");
}
Set<Value> values = null;
if (node.hasProperty(name)) {
values = Arrays.stream(node.getProperty(name).getValues()).map(v -> {
if (PropertyType.REFERENCE == v.getType() && weakReference) {
try {
Node n = JcrPropertyUtil.asValue(v, node.getSession());
return n.getSession().getValueFactory().createValue(n, true);
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to add to set property: " + name + "->" + value, e);
}
} else {
return v;
}
}).collect(Collectors.toSet());
} else {
values = new HashSet<>();
}
Value newVal = createValue(node.getSession(), value, weakReference);
boolean result = values.add(newVal);
if (weakReference) {
Property property = node.setProperty(name, (Value[]) values.stream().toArray(size -> new Value[size]), PropertyType.WEAKREFERENCE);
} else {
Property property = node.setProperty(name, (Value[]) values.stream().toArray(size -> new Value[size]));
}
return result;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to add to set property: " + name + "->" + value, e);
}
}
public static boolean removeAllFromSetProperty(Node node, String name) {
try {
JcrMetadataAccess.ensureCheckoutNode(node);
if (node == null) {
throw new IllegalArgumentException("Cannot remove a property from a null-node!");
}
if (name == null) {
throw new IllegalArgumentException("Cannot remove a property without a provided name");
}
Set<Value> values = new HashSet<>();
node.setProperty(name, (Value[]) values.stream().toArray(size -> new Value[size]));
return true;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to remove set property: " + name, e);
}
}
public static boolean removeFromSetProperty(Node node, String name, Object value) {
return removeFromSetProperty(node, name, value, false);
}
public static boolean removeFromSetProperty(Node node, String name, Object value, boolean weakRef) {
try {
JcrMetadataAccess.ensureCheckoutNode(node);
if (node == null) {
throw new IllegalArgumentException("Cannot remove a property from a null-node!");
}
if (name == null) {
throw new IllegalArgumentException("Cannot remove a property without a provided name");
}
Set<Value> values = new HashSet<>();
if (node.hasProperty(name)) {
values = Arrays.stream(node.getProperty(name).getValues()).collect(Collectors.toSet());
} else {
values = new HashSet<>();
}
Value existingVal = createValue(node.getSession(), value, weakRef);
boolean result = values.remove(existingVal);
node.setProperty(name, (Value[]) values.stream().toArray(size -> new Value[size]));
return result;
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to remove from set property: " + name + "->" + value, e);
}
}
public static <T> Set<T> getSetProperty(Node node, String name) {
try {
if (node == null) {
throw new IllegalArgumentException("Cannot set a property on a null-node!");
}
if (name == null) {
throw new IllegalArgumentException("Cannot set a property without a provided name");
}
if (node.hasProperty(name)) {
Set<T> result = new HashSet<T>((Collection<T>) getProperty(node, name));
return result;
} else {
return Collections.emptySet();
}
} catch (ClassCastException e) {
throw new MetadataRepositoryException("Wrong property data type for set", e);
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get set property: " + name, e);
}
}
public static Value createValue(Session session, Object value) {
return createValue(session, value, false);
}
public static Value createValue(Session session, Object value, boolean weakRef) {
try {
ValueFactory factory = session.getValueFactory();
if (value == null) {
throw new IllegalArgumentException("Cannot create a value from null");
} else if (value instanceof Enum) {
return factory.createValue(((Enum) value).name());
} else if (value instanceof JcrObject) {
return factory.createValue(((JcrObject) value).getNode(), weakRef);
// return factory.createValue(((JcrObject) value).getNode().getIdentifier(), weakRef ? PropertyType.WEAKREFERENCE : PropertyType.REFERENCE);
} else if (value instanceof Value) {
return (Value) value;
} else if (value instanceof Node) {
return factory.createValue((Node) value, weakRef);
// return factory.createValue(((Node) value).getIdentifier(), weakRef ? PropertyType.WEAKREFERENCE : PropertyType.REFERENCE);
} else if (value instanceof Binary) {
return factory.createValue((Binary) value);
} else if (value instanceof Calendar) {
return factory.createValue((Calendar) value);
} else if (value instanceof DateTime) {
Calendar cal = Calendar.getInstance();
cal.setTime(((DateTime) value).toDate());
return factory.createValue(cal);
} else if (value instanceof Date) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) value);
return factory.createValue(cal);
} else if (value instanceof BigDecimal) {
return factory.createValue((BigDecimal) value);
} else if (value instanceof Long) {
return factory.createValue(((Long) value).longValue());
} else if (value instanceof Double) {
return factory.createValue((Double) value);
} else if (value instanceof Boolean) {
return factory.createValue((Boolean) value);
} else if (value instanceof InputStream) {
return factory.createValue((InputStream) value);
// } else if (value instanceof Collection) {
// String[] list = new String[((Collection<Object>) value).size()];
// int pos = 0;
// for (Object cal : (Collection<Object>) value) {
// list[pos] = cal.toString();
// pos += 1;
// }
// return factory.createValue(list);
} else {
return factory.createValue(value.toString());
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to create value frpm: " + value, e);
}
}
public static int getJCRPropertyType(Object obj) {
if (obj instanceof String) {
return PropertyType.STRING;
}
if (obj instanceof Double) {
return PropertyType.DOUBLE;
}
if (obj instanceof Float) {
return PropertyType.DOUBLE;
}
if (obj instanceof Long) {
return PropertyType.LONG;
}
if (obj instanceof Integer) {
return PropertyType.LONG;
}
if (obj instanceof Boolean) {
return PropertyType.BOOLEAN;
}
if (obj instanceof Calendar) {
return PropertyType.DATE;
}
if (obj instanceof Binary) {
return PropertyType.BINARY;
}
if (obj instanceof InputStream) {
return PropertyType.BINARY;
}
if (obj instanceof JcrExtensiblePropertyCollection) {
return JcrExtensiblePropertyCollection.COLLECTION_TYPE;
}
if (obj instanceof Node) {
return PropertyType.REFERENCE;
}
return PropertyType.UNDEFINED;
}
public static Value asValue(ValueFactory factory, Object obj) {
// STRING, BOOLEAN, LONG, DOUBLE, PATH, ENTITY
try {
switch (getJCRPropertyType(obj)) {
case PropertyType.STRING:
return factory.createValue((String) obj);
case PropertyType.BOOLEAN:
return factory.createValue((Boolean) obj);
case PropertyType.DATE:
return factory.createValue((Calendar) obj);
case PropertyType.LONG:
return obj instanceof Long ? factory.createValue(((Long) obj).longValue()) : factory.createValue(((Integer) obj).longValue());
case PropertyType.DOUBLE:
return obj instanceof Double ? factory.createValue((Double) obj) : factory.createValue(((Float) obj).doubleValue());
case PropertyType.BINARY:
return factory.createValue((InputStream) obj);
case PropertyType.REFERENCE:
return factory.createValue((Node) obj);
default:
return (obj != null ? factory.createValue(obj.toString()) : factory.createValue(StringUtils.EMPTY));
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Invalid value format", e);
}
}
/**
* Assuming the specified property is a (WEAK)REFERENCE type, returns whether it is pointing at the specified node.
*/
public static boolean isReferencing(Node node, String refProp, Node targetNode) {
try {
return node.getProperty(refProp).getNode().isSame(targetNode);
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to check reference property against node: " + node, e);
}
}
/**
* Assuming the specified property is a (WEAK)REFERENCE type, returns whether it is pointing at the specified node ID.
*/
public static boolean isReferencing(Node node, String refProp, String nodeId) {
try {
return node.getProperty(refProp).getNode().getIdentifier().equals(nodeId);
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to check reference property against node ID: " + nodeId, e);
}
}
/**
* Gets the user-defined fields for the specified type.
*
* @param name the type name
* @param extensibleTypeProvider the type provider
* @return the user-defined fields
*/
@Nonnull
public static Set<UserFieldDescriptor> getUserFields(@Nonnull final String name, @Nonnull final ExtensibleTypeProvider extensibleTypeProvider) {
final ExtensibleType type = extensibleTypeProvider.getType(name);
return (type != null) ? type.getUserFieldDescriptors() : Collections.emptySet();
}
/**
* Sets the user-defined fields for the specified type.
*
* @param name the type name
* @param fields the user-defined fields
* @param extensibleTypeProvider the type provider
*/
public static void setUserFields(@Nonnull final String name, @Nonnull final Set<UserFieldDescriptor> fields, @Nonnull final ExtensibleTypeProvider extensibleTypeProvider) {
// Get type builder
final ExtensibleTypeBuilder builder;
final ExtensibleType type = extensibleTypeProvider.getType(name);
if (type == null) {
builder = extensibleTypeProvider.buildType(name);
} else {
builder = extensibleTypeProvider.updateType(type.getId());
}
// Add fields to type
final String prefix = JcrMetadataAccess.USR_PREFIX + ":";
fields.forEach(field -> {
// Encode field name
final String systemName;
try {
systemName = prefix + URLEncoder.encode(field.getSystemName(), USER_PROPERTY_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e.toString(), e);
}
// Create field descriptor
final FieldDescriptorBuilder fieldBuilder = builder.field(systemName);
fieldBuilder.description(field.getDescription());
fieldBuilder.displayName(field.getDisplayName());
fieldBuilder.type(FieldDescriptor.Type.STRING);
fieldBuilder.property(JcrUserFieldDescriptor.ORDER, Integer.toString(field.getOrder()));
fieldBuilder.property(JcrUserFieldDescriptor.REQUIRED, Boolean.toString(field.isRequired()));
fieldBuilder.add();
});
builder.build();
}
/**
* Gets the user-defined property names and values for the specified node.
*
* @param node the node to be searched
* @return a map of property names to values
* @throws IllegalStateException if a property name is encoded incorrectly
* @throws MetadataRepositoryException if the metadata repository is unavailable
*/
@Nonnull
public static Map<String, String> getUserProperties(@Nonnull final Node node) {
// Get node properties
final PropertyIterator iterator;
try {
iterator = node.getProperties();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get properties for node: " + node, e);
}
// Convert iterator to map
final String prefix = JcrMetadataAccess.USR_PREFIX + ":";
final int prefixLength = prefix.length();
final Map<String, String> properties = new HashMap<>((int) Math.min(iterator.getSize(), Integer.MAX_VALUE));
while (iterator.hasNext()) {
final Property property = iterator.nextProperty();
try {
if (property.getName().startsWith(prefix)) {
properties.put(URLDecoder.decode(property.getName().substring(prefixLength), USER_PROPERTY_ENCODING), property.getString());
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to access property \"" + property + "\" on node: " + node, e);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Unsupported encoding for property \"" + property + "\" on node: " + node, e);
}
}
return properties;
}
/**
* Sets the specified user-defined properties on the specified node.
*
* @param node the target node
* @param fields the predefined user fields
* @param properties the map of user-defined property names to values
* @throws IllegalStateException if a property name is encoded incorrectly
* @throws MetadataRepositoryException if the metadata repository is unavailable
*/
public static void setUserProperties(@Nonnull final Node node, @Nonnull final Set<UserFieldDescriptor> fields, @Nonnull final Map<String, String> properties) {
// Verify required properties are not empty
for (final UserFieldDescriptor field : fields) {
if (field.isRequired() && StringUtils.isEmpty(properties.get(field.getSystemName()))) {
throw new MissingUserPropertyException("Missing required property: " + field.getSystemName());
}
}
// Set properties on node
final Set<String> newProperties = new HashSet<>(properties.size());
final String prefix = JcrMetadataAccess.USR_PREFIX + ":";
properties.forEach((key, value) -> {
try {
final String name = prefix + URLEncoder.encode(key, USER_PROPERTY_ENCODING);
newProperties.add(name);
node.setProperty(name, value);
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to set user property \"" + key + "\" on node: " + node, e);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e.toString(), e);
}
});
// Get node properties
final PropertyIterator iterator;
try {
iterator = node.getProperties();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get properties for node: " + node, e);
}
// Remove properties from node
while (iterator.hasNext()) {
final Property property = iterator.nextProperty();
try {
final String name = property.getName();
if (name.startsWith(prefix) && !newProperties.contains(name)) {
property.remove();
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to remove property \"" + property + "\" on node: " + node, e);
}
}
}
/**
* Copies a property, if present, from the source node to the destination node.
*
* @param src source node
* @param dest destination node
* @param name the name of the property
* @return whether the source had a value to copy
*/
public static boolean copyProperty(Node src, Node dest, String name) {
try {
if (src.hasProperty(name)) {
Property prop = src.getProperty(name);
if (prop.isMultiple()) {
Value[] values = prop.getValues();
dest.setProperty(name, values, prop.getType());
} else {
Value value = prop.getValue();
dest.setProperty(name, value, prop.getType());
}
return true;
} else {
return false;
}
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to copy property \"" + name + "\" from node " + src + " to " + dest, e);
}
}
public static Node getParent(Property prop) {
try {
return prop.getParent();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get the parent node of the property: " + prop, e);
}
}
@SuppressWarnings("unchecked")
public static Iterable<Property> propertiesIterable(Node node) {
return () -> {
try {
return node.getProperties();
} catch (RepositoryException e) {
throw new MetadataRepositoryException("Failed to get the properties of node: " + node, e);
}
};
}
/**
* @param node
*/
public static Stream<Property> streamProperties(Node node) {
return StreamSupport.stream(propertiesIterable(node).spliterator(), false);
}
}