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 * @throws RepositoryException 063 */ 064 public Property appendOrReplaceNodeProperty(final Node node, final String propertyName, final Value newValue) 065 throws RepositoryException { 066 067 final Property property; 068 069 // if it already exists, we can take some shortcuts 070 if (node.hasProperty(propertyName)) { 071 072 property = node.getProperty(propertyName); 073 074 if (property.isMultiple()) { 075 LOGGER.debug("Appending value {} to {} property {}", newValue, 076 PropertyType.nameFromValue(property.getType()), 077 propertyName); 078 079 // if the property is multi-valued, go ahead and append to it. 080 final List<Value> newValues = new ArrayList<>(); 081 Collections.addAll(newValues, 082 node.getProperty(propertyName).getValues()); 083 084 if (!newValues.contains(newValue)) { 085 newValues.add(newValue); 086 property.setValue(toArray(newValues, Value.class)); 087 } 088 } else { 089 // or we'll just overwrite it 090 LOGGER.debug("Overwriting {} property {} with new value {}", PropertyType.nameFromValue(property 091 .getType()), propertyName, newValue); 092 property.setValue(newValue); 093 } 094 } else { 095 boolean isMultiple = true; 096 try { 097 isMultiple = isMultivaluedProperty(node, propertyName); 098 099 } catch (final NoSuchPropertyDefinitionException e) { 100 // simply represents a new kind of property on this node 101 } 102 if (isMultiple) { 103 LOGGER.debug("Creating new multivalued {} property {} with " + 104 "initial value [{}]", 105 PropertyType.nameFromValue(newValue.getType()), 106 propertyName, newValue); 107 property = node.setProperty(propertyName, new Value[]{newValue}, newValue.getType()); 108 } else { 109 LOGGER.debug("Creating new single-valued {} property {} with " + 110 "initial value {}", 111 PropertyType.nameFromValue(newValue.getType()), 112 propertyName, newValue); 113 property = node.setProperty(propertyName, newValue, newValue.getType()); 114 } 115 } 116 117 if (!property.isMultiple() && !isInternalReferenceProperty.apply(property)) { 118 final String referencePropertyName = getReferencePropertyName(propertyName); 119 if (node.hasProperty(referencePropertyName)) { 120 node.setProperty(referencePropertyName, (Value[]) null); 121 } 122 } 123 124 return property; 125 } 126 127 /** 128 * Add a reference placeholder from one node to another in-domain resource 129 * @param idTranslator 130 * @param node 131 * @param propertyName 132 * @param resource 133 * @throws RepositoryException 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 (isExternal.apply(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.setProperty(referencePropertyName, (Value[]) null); 153 } 154 155 if (node.hasProperty(propertyName)) { 156 node.setProperty(propertyName, (Value) null); 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 171 * @param node 172 * @param propertyName 173 * @param resource 174 * @throws RepositoryException 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 197 */ 198 public Property removeNodeProperty(final Node node, final String propertyName, final Value valueToRemove) 199 throws RepositoryException { 200 final Property property; 201 202 // if the property doesn't exist, we don't need to worry about it. 203 if (node.hasProperty(propertyName)) { 204 205 property = node.getProperty(propertyName); 206 207 if (JcrPropertyFunctions.isMultipleValuedProperty.apply(property)) { 208 209 final List<Value> newValues = new ArrayList<>(); 210 211 boolean remove = false; 212 213 for (final Value v : node.getProperty(propertyName).getValues()) { 214 if (v.equals(valueToRemove)) { 215 remove = true; 216 } else { 217 newValues.add(v); 218 } 219 } 220 221 // we only need to update the property if we did anything. 222 if (remove) { 223 if (newValues.isEmpty()) { 224 LOGGER.debug("Removing property {}", propertyName); 225 property.setValue((Value[])null); 226 } else { 227 LOGGER.debug("Removing value {} from property {}", 228 valueToRemove, propertyName); 229 property 230 .setValue(toArray(newValues, Value.class)); 231 } 232 } 233 234 } else { 235 if (property.getValue().equals(valueToRemove)) { 236 LOGGER.debug("Removing value from property {}", propertyName); 237 property.setValue((Value)null); 238 } 239 } 240 } else { 241 property = null; 242 } 243 244 return property; 245 } 246}