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