001/* 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.kernel.modeshape.utils; 017 018import static java.util.Arrays.asList; 019import static java.util.Arrays.stream; 020import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.getReferencePropertyName; 021import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isExternalNode; 022import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isInternalReferenceProperty; 023import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isMultivaluedProperty; 024import static org.fcrepo.kernel.modeshape.utils.UncheckedPredicate.uncheck; 025import static org.slf4j.LoggerFactory.getLogger; 026 027import java.util.ArrayList; 028import java.util.List; 029import java.util.concurrent.atomic.AtomicBoolean; 030 031import org.apache.commons.lang3.StringUtils; 032 033import javax.jcr.Node; 034import javax.jcr.Property; 035import javax.jcr.PropertyType; 036import javax.jcr.RepositoryException; 037import javax.jcr.Value; 038 039import com.hp.hpl.jena.rdf.model.Resource; 040 041import org.fcrepo.kernel.api.models.FedoraResource; 042import org.fcrepo.kernel.api.exception.IdentifierConversionException; 043import org.fcrepo.kernel.api.exception.NoSuchPropertyDefinitionException; 044import org.fcrepo.kernel.api.identifiers.IdentifierConverter; 045import org.slf4j.Logger; 046 047/** 048 * Tools for replacing, appending and deleting JCR node properties 049 * @author Chris Beer 050 * @author ajs6f 051 * @since May 10, 2013 052 */ 053public class NodePropertiesTools { 054 055 private static final Logger LOGGER = getLogger(NodePropertiesTools.class); 056 057 /** 058 * Given a JCR node, property and value, either: 059 * - if the property is single-valued, replace the existing property with 060 * the new value 061 * - if the property is multivalued, append the new value to the property 062 * @param node the JCR node 063 * @param propertyName a name of a JCR property (either pre-existing or 064 * otherwise) 065 * @param newValue the JCR value to insert 066 * @throws RepositoryException if repository exception occurred 067 */ 068 public void appendOrReplaceNodeProperty(final Node node, final String propertyName, final Value newValue) 069 throws RepositoryException { 070 071 final Property property; 072 073 // if it already exists, we can take some shortcuts 074 if (node.hasProperty(propertyName)) { 075 076 property = node.getProperty(propertyName); 077 078 if (property.isMultiple()) { 079 LOGGER.debug("Appending value {} to {} property {}", newValue, 080 PropertyType.nameFromValue(property.getType()), 081 propertyName); 082 083 // if the property is multi-valued, go ahead and append to it. 084 final List<Value> newValues = new ArrayList<>(asList(node.getProperty(propertyName).getValues())); 085 086 if (!newValues.contains(newValue)) { 087 newValues.add(newValue); 088 property.setValue(newValues.toArray(new Value[newValues.size()])); 089 } 090 } else { 091 // or we'll just overwrite its single value 092 LOGGER.debug("Overwriting {} property {} with new value {}", PropertyType.nameFromValue(property 093 .getType()), propertyName, newValue); 094 property.setValue(newValue); 095 } 096 } else { 097 // we're creating a new property on this node, so we check whether it should be multi-valued 098 boolean isMultiple = true; 099 try { 100 isMultiple = isMultivaluedProperty(node, propertyName); 101 } catch (final NoSuchPropertyDefinitionException e) { 102 // simply represents a new kind of property on this node 103 } 104 if (isMultiple) { 105 LOGGER.debug("Creating new multivalued {} property {} with " + 106 "initial value [{}]", 107 PropertyType.nameFromValue(newValue.getType()), 108 propertyName, newValue); 109 property = node.setProperty(propertyName, new Value[]{newValue}, newValue.getType()); 110 } else { 111 LOGGER.debug("Creating new single-valued {} property {} with " + 112 "initial value {}", 113 PropertyType.nameFromValue(newValue.getType()), 114 propertyName, newValue); 115 property = node.setProperty(propertyName, newValue, newValue.getType()); 116 } 117 } 118 119 if (!property.isMultiple() && !isInternalReferenceProperty.test(property)) { 120 final String referencePropertyName = getReferencePropertyName(propertyName); 121 if (node.hasProperty(referencePropertyName)) { 122 node.getProperty(referencePropertyName).remove(); 123 } 124 } 125 } 126 127 /** 128 * Add a reference placeholder from one node to another in-domain resource 129 * @param idTranslator the id translator 130 * @param node the node 131 * @param propertyName the property name 132 * @param resource the resource 133 * @throws RepositoryException if repository exception occurred 134 */ 135 public void addReferencePlaceholders(final IdentifierConverter<Resource,FedoraResource> idTranslator, 136 final Node node, 137 final String propertyName, 138 final Resource resource) throws RepositoryException { 139 140 try { 141 final Node refNode = idTranslator.convert(resource).getNode(); 142 143 if (isExternalNode.test(refNode)) { 144 // we can't apply REFERENCE properties to external resources 145 return; 146 } 147 148 final String referencePropertyName = getReferencePropertyName(propertyName); 149 150 if (!isMultivaluedProperty(node, propertyName)) { 151 if (node.hasProperty(referencePropertyName)) { 152 node.getProperty(referencePropertyName).remove(); 153 } 154 155 if (node.hasProperty(propertyName)) { 156 node.getProperty(propertyName).remove(); 157 } 158 } 159 160 final Value v = node.getSession().getValueFactory().createValue(refNode, true); 161 appendOrReplaceNodeProperty(node, referencePropertyName, v); 162 163 } catch (final IdentifierConversionException e) { 164 // no-op 165 } 166 } 167 168 /** 169 * Remove a reference placeholder that links one node to another in-domain resource 170 * @param idTranslator the id translator 171 * @param node the node 172 * @param propertyName the property name 173 * @param resource the resource 174 * @throws RepositoryException if repository exception occurred 175 */ 176 public void removeReferencePlaceholders(final IdentifierConverter<Resource,FedoraResource> idTranslator, 177 final Node node, 178 final String propertyName, 179 final Resource resource) throws RepositoryException { 180 181 final String referencePropertyName = getReferencePropertyName(propertyName); 182 183 final Node refNode = idTranslator.convert(resource).getNode(); 184 final Value v = node.getSession().getValueFactory().createValue(refNode, true); 185 removeNodeProperty(node, referencePropertyName, v); 186 } 187 /** 188 * Given a JCR node, property and value, remove the value (if it exists) 189 * from the property, and remove the 190 * property if no values remove 191 * 192 * @param node the JCR node 193 * @param propertyName a name of a JCR property (either pre-existing or 194 * otherwise) 195 * @param valueToRemove the JCR value to remove 196 * @throws RepositoryException if repository exception occurred 197 */ 198 public void removeNodeProperty(final Node node, final String propertyName, final Value valueToRemove) 199 throws RepositoryException { 200 LOGGER.debug("Request to remove {}", valueToRemove); 201 // if the property doesn't exist, we don't need to worry about it. 202 if (node.hasProperty(propertyName)) { 203 204 final Property property = node.getProperty(propertyName); 205 final String strValueToRemove = valueToRemove.getString(); 206 207 if (property.isMultiple()) { 208 final AtomicBoolean remove = new AtomicBoolean(); 209 final Value[] newValues = stream(node.getProperty(propertyName).getValues()).filter(uncheck(v -> { 210 final String strVal = v.getString(); 211 212 LOGGER.debug("v is '{}', valueToRemove is '{}'", v, strValueToRemove ); 213 if (strVal.equals(strValueToRemove)) { 214 remove.set(true); 215 return false; 216 } 217 218 return true; 219 })).toArray(Value[]::new); 220 221 // we only need to update the property if we did anything. 222 if (remove.get()) { 223 if (newValues.length == 0) { 224 LOGGER.debug("Removing property '{}'", propertyName); 225 property.remove(); 226 } else { 227 LOGGER.debug("Removing value '{}' from property '{}'", strValueToRemove, propertyName); 228 property.setValue(newValues); 229 } 230 } else { 231 LOGGER.debug("Value not removed from property name '{}' (value '{}')", propertyName, 232 strValueToRemove); 233 throw new RepositoryException ("Property '" + propertyName + "': Unable to remove value '" + 234 StringUtils.substring(strValueToRemove, 0, 50) + "...'"); 235 } 236 } else { 237 238 final String strPropVal = property.getValue().getString(); 239 240 LOGGER.debug("Removing string '{}'", strValueToRemove); 241 if (StringUtils.equals(strPropVal, strValueToRemove)) { 242 LOGGER.debug("single value: Removing value from property '{}'", propertyName); 243 property.remove(); 244 } else { 245 LOGGER.debug("Value not removed from property name '{}' (property value: '{}';compare value: '{}')", 246 propertyName, strPropVal, strValueToRemove); 247 throw new RepositoryException("Property '" + propertyName + "': Unable to remove value '" + 248 StringUtils.substring(strValueToRemove, 0, 50) + "'"); 249 } 250 } 251 } 252 } 253}