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