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 com.google.common.base.Predicate; 019import org.fcrepo.kernel.FedoraJcrTypes; 020import org.fcrepo.kernel.models.FedoraResource; 021import org.fcrepo.kernel.exception.RepositoryRuntimeException; 022import org.fcrepo.kernel.services.functions.AnyTypesPredicate; 023import org.fcrepo.kernel.services.functions.JcrPropertyFunctions; 024import org.slf4j.Logger; 025 026import javax.jcr.Node; 027import javax.jcr.Property; 028import javax.jcr.RepositoryException; 029import javax.jcr.Session; 030import javax.jcr.nodetype.NodeType; 031import javax.jcr.nodetype.PropertyDefinition; 032 033import static com.google.common.base.Preconditions.checkNotNull; 034import static javax.jcr.PropertyType.REFERENCE; 035import static javax.jcr.PropertyType.UNDEFINED; 036import static javax.jcr.PropertyType.WEAKREFERENCE; 037import static org.slf4j.LoggerFactory.getLogger; 038 039/** 040 * Convenience class with static methods for manipulating Fedora types in the 041 * JCR. 042 * 043 * @author ajs6f 044 * @since Feb 14, 2013 045 */ 046public abstract class FedoraTypesUtils implements FedoraJcrTypes { 047 048 public static final String REFERENCE_PROPERTY_SUFFIX = "_ref"; 049 050 private static final Logger LOGGER = getLogger(FedoraTypesUtils.class); 051 052 /** 053 * Predicate for determining whether this {@link Node} is a {@link org.fcrepo.kernel.models.Container}. 054 */ 055 public static Predicate<Node> isContainer = 056 new AnyTypesPredicate(FEDORA_CONTAINER); 057 058 /** 059 * Predicate for determining whether this {@link Node} is a {@link org.fcrepo.kernel.models.NonRdfSourceDescription} 060 */ 061 public static Predicate<Node> isNonRdfSourceDescription = 062 new AnyTypesPredicate(FEDORA_NON_RDF_SOURCE_DESCRIPTION); 063 064 065 /** 066 * Predicate for determining whether this {@link Node} is a Fedora 067 * binary. 068 */ 069 public static Predicate<Node> isFedoraBinary = 070 new AnyTypesPredicate(FEDORA_BINARY); 071 072 /** 073 * Predicate for determining whether this {@link FedoraResource} has a frozen node 074 */ 075 public static Predicate<FedoraResource> isFrozenNode = 076 new Predicate<FedoraResource>() { 077 078 @Override 079 public boolean apply(final FedoraResource f) { 080 return f.hasType(FROZEN_NODE) || f.getPath().contains(JCR_FROZEN_NODE); 081 } 082 }; 083 084 /** 085 * Predicate for determining whether this {@link Node} is a Fedora 086 * binary. 087 */ 088 public static Predicate<Node> isBlankNode = 089 new AnyTypesPredicate(FEDORA_BLANKNODE); 090 091 /** 092 * Check if a property is a reference property. 093 */ 094 public static Predicate<Property> isInternalReferenceProperty = 095 new Predicate<Property>() { 096 097 @Override 098 public boolean apply(final Property p) { 099 try { 100 return (p.getType() == REFERENCE || p.getType() == WEAKREFERENCE) 101 && p.getName().endsWith(REFERENCE_PROPERTY_SUFFIX); 102 } catch (final RepositoryException e) { 103 throw new RepositoryRuntimeException(e); 104 } 105 } 106 }; 107 108 /** 109 * Check whether a property is an internal property that should be suppressed 110 * from external output. 111 */ 112 public static Predicate<Property> isInternalProperty = 113 new Predicate<Property>() { 114 115 @Override 116 public boolean apply(final Property p) { 117 return JcrPropertyFunctions.isBinaryContentProperty.apply(p) 118 || isProtectedAndShouldBeHidden.apply(p); 119 } 120 }; 121 122 /** 123 * Check whether a property is protected (ie, cannot be modified directly) but 124 * is not one we've explicitly chosen to include. 125 */ 126 public static Predicate<Property> isProtectedAndShouldBeHidden = new ProtectedAndHiddenCheck(); 127 128 private static class ProtectedAndHiddenCheck implements Predicate<Property> { 129 130 @Override 131 public boolean apply(final Property p) { 132 try { 133 if (!p.getDefinition().isProtected()) { 134 return false; 135 } else if (p.getParent().isNodeType(FROZEN_NODE)) { 136 // everything on a frozen node is protected 137 // but we wish to display it anyway and there's 138 // another mechanism in place to make clear that 139 // things cannot be edited. 140 return false; 141 } else { 142 final String name = p.getName(); 143 for (String exposedName : EXPOSED_PROTECTED_JCR_TYPES) { 144 if (name.equals(exposedName)) { 145 return false; 146 } 147 } 148 return true; 149 } 150 } catch (final RepositoryException e) { 151 throw new RepositoryRuntimeException(e); 152 } 153 } 154 } 155 156 /** 157 * Check if a node is "internal" and should not be exposed e.g. via the REST 158 * API 159 */ 160 public static Predicate<Node> isInternalNode = new Predicate<Node>() { 161 162 @Override 163 public boolean apply(final Node n) { 164 checkNotNull(n, "null is neither internal nor not internal!"); 165 try { 166 return n.isNodeType("mode:system"); 167 } catch (final RepositoryException e) { 168 throw new RepositoryRuntimeException(e); 169 } 170 } 171 }; 172 173 /** 174 * Get the JCR property type ID for a given property name. If unsure, mark 175 * it as UNDEFINED. 176 * 177 * @param node the JCR node to add the property on 178 * @param propertyName the property name 179 * @return a PropertyType value 180 * @throws RepositoryException if repository exception occurred 181 */ 182 public static int getPropertyType(final Node node, final String propertyName) 183 throws RepositoryException { 184 LOGGER.debug("Getting type of property: {} from node: {}", 185 propertyName, node); 186 final PropertyDefinition def = 187 getDefinitionForPropertyName(node, propertyName); 188 189 if (def == null) { 190 return UNDEFINED; 191 } 192 193 return def.getRequiredType(); 194 } 195 196 /** 197 * Determine if a given JCR property name is single- or multi- valued. 198 * If unsure, choose the least restrictive 199 * option (multivalued) 200 * 201 * @param node the JCR node to check 202 * @param propertyName the property name 203 * (which may or may not already exist) 204 * @return true if the property is (or could be) multivalued 205 * @throws RepositoryException if repository exception occurred 206 */ 207 public static boolean isMultivaluedProperty(final Node node, 208 final String propertyName) 209 throws RepositoryException { 210 final PropertyDefinition def = 211 getDefinitionForPropertyName(node, propertyName); 212 213 if (def == null) { 214 return true; 215 } 216 217 return def.isMultiple(); 218 } 219 220 /** 221 * Get the property definition information (containing type and multi-value 222 * information) 223 * 224 * @param node the node to use for inferring the property definition 225 * @param propertyName the property name to retrieve a definition for 226 * @return a JCR PropertyDefinition, if available, or null 227 * @throws javax.jcr.RepositoryException if repository exception occurred 228 */ 229 public static PropertyDefinition getDefinitionForPropertyName(final Node node, 230 final String propertyName) 231 throws RepositoryException { 232 233 final NodeType primaryNodeType = node.getPrimaryNodeType(); 234 final PropertyDefinition[] propertyDefinitions = primaryNodeType.getPropertyDefinitions(); 235 LOGGER.debug("Looking for property name: {}", propertyName); 236 for (final PropertyDefinition p : propertyDefinitions) { 237 LOGGER.debug("Checking property: {}", p.getName()); 238 if (p.getName().equals(propertyName)) { 239 return p; 240 } 241 } 242 243 for (final NodeType nodeType : node.getMixinNodeTypes()) { 244 for (final PropertyDefinition p : nodeType.getPropertyDefinitions()) { 245 if (p.getName().equals(propertyName)) { 246 return p; 247 } 248 } 249 } 250 return null; 251 } 252 253 /** 254 * When we add certain URI properties, we also want to leave a reference node 255 * @param propertyName the property name 256 * @return property name as a reference 257 */ 258 public static String getReferencePropertyName(final String propertyName) { 259 return propertyName + REFERENCE_PROPERTY_SUFFIX; 260 } 261 262 /** 263 * Given an internal reference node property, get the original name 264 * @param refPropertyName the reference node property name 265 * @return original property name of the reference property 266 */ 267 public static String getReferencePropertyOriginalName(final String refPropertyName) { 268 final int i = refPropertyName.lastIndexOf(REFERENCE_PROPERTY_SUFFIX); 269 270 if (i < 0) { 271 return refPropertyName; 272 } 273 return refPropertyName.substring(0, i); 274 } 275 276 /** 277 * Check if a property definition is a reference property 278 * @param node the given node 279 * @param propertyName the property name 280 * @return whether a property definition is a reference property 281 * @throws RepositoryException if repository exception occurred 282 */ 283 public static boolean isReferenceProperty(final Node node, final String propertyName) throws RepositoryException { 284 final PropertyDefinition propertyDefinition = getDefinitionForPropertyName(node, propertyName); 285 286 return propertyDefinition != null && 287 (propertyDefinition.getRequiredType() == REFERENCE 288 || propertyDefinition.getRequiredType() == WEAKREFERENCE); 289 } 290 291 292 /** 293 * Get the closest ancestor that current exists 294 * 295 * @param session the given session 296 * @param path the given path 297 * @return the closest ancestor that current exists 298 * @throws RepositoryException if repository exception occurred 299 */ 300 public static Node getClosestExistingAncestor(final Session session, 301 final String path) throws RepositoryException { 302 final String[] pathSegments = path.replaceAll("^/+", "").replaceAll("/+$", "").split("/"); 303 304 final StringBuilder existingAncestorPath = new StringBuilder(path.length()); 305 existingAncestorPath.append("/"); 306 307 final int len = pathSegments.length; 308 for (int i = 0; i != len; ++i) { 309 final String pathSegment = pathSegments[i]; 310 311 if (session.nodeExists(existingAncestorPath.toString() + pathSegment)) { 312 // Add to existingAncestorPath ... 313 existingAncestorPath.append(pathSegment); 314 if (i != (len - 1)) { 315 existingAncestorPath.append("/"); 316 } 317 } else { 318 if (i != 0) { 319 existingAncestorPath.deleteCharAt(existingAncestorPath.length() - 1); 320 } 321 break; 322 } 323 324 } 325 326 return session.getNode(existingAncestorPath.toString()); 327 } 328 329}