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.slf4j.LoggerFactory.getLogger; 025 026import java.util.ArrayList; 027import java.util.List; 028import java.util.concurrent.atomic.AtomicBoolean; 029 030import javax.jcr.Node; 031import javax.jcr.Property; 032import javax.jcr.PropertyType; 033import javax.jcr.RepositoryException; 034import javax.jcr.Value; 035 036import com.hp.hpl.jena.rdf.model.Resource; 037 038import org.fcrepo.kernel.api.models.FedoraResource; 039import org.fcrepo.kernel.api.exception.IdentifierConversionException; 040import org.fcrepo.kernel.api.exception.NoSuchPropertyDefinitionException; 041import org.fcrepo.kernel.api.identifiers.IdentifierConverter; 042import org.slf4j.Logger; 043 044/** 045 * Tools for replacing, appending and deleting JCR node properties 046 * @author Chris Beer 047 * @author ajs6f 048 * @since May 10, 2013 049 */ 050public class NodePropertiesTools { 051 052 private static final Logger LOGGER = getLogger(NodePropertiesTools.class); 053 054 /** 055 * Given a JCR node, property and value, either: 056 * - if the property is single-valued, replace the existing property with 057 * the new value 058 * - if the property is multivalued, append the new value to the property 059 * @param node the JCR node 060 * @param propertyName a name of a JCR property (either pre-existing or 061 * otherwise) 062 * @param newValue the JCR value to insert 063 * @throws RepositoryException if repository exception occurred 064 */ 065 public void appendOrReplaceNodeProperty(final Node node, final String propertyName, final Value newValue) 066 throws RepositoryException { 067 068 final Property property; 069 070 // if it already exists, we can take some shortcuts 071 if (node.hasProperty(propertyName)) { 072 073 property = node.getProperty(propertyName); 074 075 if (property.isMultiple()) { 076 LOGGER.debug("Appending value {} to {} property {}", newValue, 077 PropertyType.nameFromValue(property.getType()), 078 propertyName); 079 080 // if the property is multi-valued, go ahead and append to it. 081 final List<Value> newValues = new ArrayList<>(asList(node.getProperty(propertyName).getValues())); 082 083 if (!newValues.contains(newValue)) { 084 newValues.add(newValue); 085 property.setValue(newValues.toArray(new Value[newValues.size()])); 086 } 087 } else { 088 // or we'll just overwrite its single value 089 LOGGER.debug("Overwriting {} property {} with new value {}", PropertyType.nameFromValue(property 090 .getType()), propertyName, newValue); 091 property.setValue(newValue); 092 } 093 } else { 094 // we're creating a new property on this node, so we check whether it should be multi-valued 095 boolean isMultiple = true; 096 try { 097 isMultiple = isMultivaluedProperty(node, propertyName); 098 } catch (final NoSuchPropertyDefinitionException e) { 099 // simply represents a new kind of property on this node 100 } 101 if (isMultiple) { 102 LOGGER.debug("Creating new multivalued {} property {} with " + 103 "initial value [{}]", 104 PropertyType.nameFromValue(newValue.getType()), 105 propertyName, newValue); 106 property = node.setProperty(propertyName, new Value[]{newValue}, newValue.getType()); 107 } else { 108 LOGGER.debug("Creating new single-valued {} property {} with " + 109 "initial value {}", 110 PropertyType.nameFromValue(newValue.getType()), 111 propertyName, newValue); 112 property = node.setProperty(propertyName, newValue, newValue.getType()); 113 } 114 } 115 116 if (!property.isMultiple() && !isInternalReferenceProperty.test(property)) { 117 final String referencePropertyName = getReferencePropertyName(propertyName); 118 if (node.hasProperty(referencePropertyName)) { 119 node.getProperty(referencePropertyName).remove(); 120 } 121 } 122 } 123 124 /** 125 * Add a reference placeholder from one node to another in-domain resource 126 * @param idTranslator the id translator 127 * @param node the node 128 * @param propertyName the property name 129 * @param resource the resource 130 * @throws RepositoryException if repository exception occurred 131 */ 132 public void addReferencePlaceholders(final IdentifierConverter<Resource,FedoraResource> idTranslator, 133 final Node node, 134 final String propertyName, 135 final Resource resource) throws RepositoryException { 136 137 try { 138 final Node refNode = idTranslator.convert(resource).getNode(); 139 140 if (isExternalNode.test(refNode)) { 141 // we can't apply REFERENCE properties to external resources 142 return; 143 } 144 145 final String referencePropertyName = getReferencePropertyName(propertyName); 146 147 if (!isMultivaluedProperty(node, propertyName)) { 148 if (node.hasProperty(referencePropertyName)) { 149 node.getProperty(referencePropertyName).remove(); 150 } 151 152 if (node.hasProperty(propertyName)) { 153 node.getProperty(propertyName).remove(); 154 } 155 } 156 157 final Value v = node.getSession().getValueFactory().createValue(refNode, true); 158 appendOrReplaceNodeProperty(node, referencePropertyName, v); 159 160 } catch (final IdentifierConversionException e) { 161 // no-op 162 } 163 } 164 165 /** 166 * Remove a reference placeholder that links one node to another in-domain resource 167 * @param idTranslator the id translator 168 * @param node the node 169 * @param propertyName the property name 170 * @param resource the resource 171 * @throws RepositoryException if repository exception occurred 172 */ 173 public void removeReferencePlaceholders(final IdentifierConverter<Resource,FedoraResource> idTranslator, 174 final Node node, 175 final String propertyName, 176 final Resource resource) throws RepositoryException { 177 178 final String referencePropertyName = getReferencePropertyName(propertyName); 179 180 final Node refNode = idTranslator.convert(resource).getNode(); 181 final Value v = node.getSession().getValueFactory().createValue(refNode, true); 182 removeNodeProperty(node, referencePropertyName, v); 183 } 184 /** 185 * Given a JCR node, property and value, remove the value (if it exists) 186 * from the property, and remove the 187 * property if no values remove 188 * 189 * @param node the JCR node 190 * @param propertyName a name of a JCR property (either pre-existing or 191 * otherwise) 192 * @param valueToRemove the JCR value to remove 193 * @throws RepositoryException if repository exception occurred 194 */ 195 public void removeNodeProperty(final Node node, final String propertyName, final Value valueToRemove) 196 throws RepositoryException { 197 198 // if the property doesn't exist, we don't need to worry about it. 199 if (node.hasProperty(propertyName)) { 200 201 final Property property = node.getProperty(propertyName); 202 203 if (property.isMultiple()) { 204 final AtomicBoolean remove = new AtomicBoolean(); 205 final Value[] newValues = stream(node.getProperty(propertyName).getValues()).filter(v -> { 206 if (v.equals(valueToRemove)) { 207 remove.set(true); 208 return false; 209 } 210 return true; 211 }).toArray(Value[]::new); 212 213 // we only need to update the property if we did anything. 214 if (remove.get()) { 215 if (newValues.length == 0) { 216 LOGGER.debug("Removing property {}", propertyName); 217 property.remove(); 218 } else { 219 LOGGER.debug("Removing value {} from property {}", valueToRemove, propertyName); 220 property.setValue(newValues); 221 } 222 } 223 224 } else { 225 if (property.getValue().equals(valueToRemove)) { 226 LOGGER.debug("Removing value from property {}", propertyName); 227 property.remove(); 228 } 229 } 230 } 231 } 232}