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 java.util.Arrays; 019import java.util.Optional; 020import java.util.function.Predicate; 021 022import org.fcrepo.kernel.api.FedoraJcrTypes; 023import org.fcrepo.kernel.api.models.FedoraResource; 024import org.fcrepo.kernel.api.services.functions.AnyTypesPredicate; 025import org.modeshape.jcr.JcrRepository; 026import org.modeshape.jcr.cache.NodeKey; 027import org.slf4j.Logger; 028 029import javax.jcr.Node; 030import javax.jcr.Property; 031import javax.jcr.RepositoryException; 032import javax.jcr.Session; 033import javax.jcr.nodetype.NodeType; 034import javax.jcr.nodetype.PropertyDefinition; 035 036import static java.util.Arrays.stream; 037import static javax.jcr.PropertyType.REFERENCE; 038import static javax.jcr.PropertyType.WEAKREFERENCE; 039import static org.fcrepo.kernel.api.services.functions.JcrPropertyFunctions.isBinaryContentProperty; 040import static org.fcrepo.kernel.api.utils.UncheckedPredicate.uncheck; 041import static org.slf4j.LoggerFactory.getLogger; 042 043/** 044 * Convenience class with static methods for manipulating Fedora types in the 045 * JCR. 046 * 047 * @author ajs6f 048 * @since Feb 14, 2013 049 */ 050public abstract class FedoraTypesUtils implements FedoraJcrTypes { 051 052 public static final String REFERENCE_PROPERTY_SUFFIX = "_ref"; 053 054 private static final Logger LOGGER = getLogger(FedoraTypesUtils.class); 055 056 /** 057 * Predicate for determining whether this {@link Node} is a {@link org.fcrepo.kernel.api.models.Container}. 058 */ 059 public static Predicate<Node> isContainer = new AnyTypesPredicate(FEDORA_CONTAINER); 060 061 /** 062 * Predicate for determining whether this {@link Node} is a 063 * {@link org.fcrepo.kernel.api.models.NonRdfSourceDescription}. 064 */ 065 public static Predicate<Node> isNonRdfSourceDescription = new AnyTypesPredicate(FEDORA_NON_RDF_SOURCE_DESCRIPTION); 066 067 068 /** 069 * Predicate for determining whether this {@link Node} is a Fedora 070 * binary. 071 */ 072 public static Predicate<Node> isFedoraBinary = new AnyTypesPredicate(FEDORA_BINARY); 073 074 /** 075 * Predicate for determining whether this {@link FedoraResource} has a frozen node 076 */ 077 public static Predicate<FedoraResource> isFrozenNode = f -> f.hasType(FROZEN_NODE) || 078 f.getPath().contains(JCR_FROZEN_NODE); 079 080 /** 081 * Predicate for determining whether this {@link Node} is a Fedora Skolem node. 082 */ 083 public static Predicate<Node> isSkolemNode = new AnyTypesPredicate(FEDORA_SKOLEM); 084 085 /** 086 * Check if a property is a reference property. 087 */ 088 public static Predicate<Property> isInternalReferenceProperty = uncheck(p -> (p.getType() == REFERENCE || 089 p.getType() == WEAKREFERENCE) && 090 p.getName().endsWith(REFERENCE_PROPERTY_SUFFIX)); 091 092 /** 093 * Check whether a property is protected (ie, cannot be modified directly) but 094 * is not one we've explicitly chosen to include. 095 */ 096 public static Predicate<Property> isProtectedAndShouldBeHidden = uncheck(p -> { 097 if (!p.getDefinition().isProtected()) { 098 return false; 099 } else if (p.getParent().isNodeType(FROZEN_NODE)) { 100 // everything on a frozen node is protected 101 // but we wish to display it anyway and there's 102 // another mechanism in place to make clear that 103 // things cannot be edited. 104 return false; 105 } else { 106 return !EXPOSED_PROTECTED_JCR_TYPES.stream().anyMatch(p.getName()::equals); 107 } 108 }); 109 110 /** 111 * Check if a property is intentionally suppressed. 112 */ 113 private static Predicate<Property> isSuppressedProperty = uncheck(p -> p.getName().equals("jcr:mimeType")); 114 115 /** 116 * Check whether a property is an internal property that should be suppressed 117 * from external output. 118 */ 119 public static Predicate<Property> isInternalProperty = p -> isBinaryContentProperty.test(p) || 120 isProtectedAndShouldBeHidden.test(p) || isSuppressedProperty.test(p); 121 122 123 /** 124 * Check if a node is "internal" and should not be exposed e.g. via the REST 125 * API 126 */ 127 public static Predicate<Node> isInternalNode = uncheck(n -> n.isNodeType("mode:system")); 128 129 /** 130 * Check if a node is externally managed. 131 * 132 * Note: modeshape uses a source-workspace-identifier scheme 133 * to identify whether a node is externally-managed. 134 * Ordinary (non-external) nodes will have simple UUIDs 135 * as an identifier. These are never external nodes. 136 * 137 * External nodes will have a 7-character hex code 138 * identifying the "source", followed by another 139 * 7-character hex code identifying the "workspace", followed 140 * by a "/" and then the rest of the "identifier". 141 * 142 * Following that scheme, if a node's "source" key does not 143 * match the repository's configured store name, then it is an 144 * external node. 145 */ 146 public static Predicate<Node> isExternalNode = uncheck(n -> { 147 if (NodeKey.isValidRandomIdentifier(n.getIdentifier())) { 148 return false; 149 } else if (n.getPrimaryNodeType().getName().equals(ROOT)) { 150 return false; 151 } else { 152 final NodeKey key = new NodeKey(n.getIdentifier()); 153 final String source = NodeKey.keyForSourceName( 154 ((JcrRepository)n.getSession().getRepository()).getConfiguration().getStoreName()); 155 return !key.getSourceKey().equals(source); 156 } 157 }); 158 159 /** 160 * Get the JCR property type ID for a given property name. If unsure, mark 161 * it as UNDEFINED. 162 * 163 * @param node the JCR node to add the property on 164 * @param propertyName the property name 165 * @return a PropertyType value 166 * @throws RepositoryException if repository exception occurred 167 */ 168 public static Optional<Integer> getPropertyType(final Node node, final String propertyName) 169 throws RepositoryException { 170 LOGGER.debug("Getting type of property: {} from node: {}", propertyName, node); 171 return getDefinitionForPropertyName(node, propertyName).map(PropertyDefinition::getRequiredType); 172 } 173 174 /** 175 * Determine if a given JCR property name is single- or multi- valued. 176 * If unsure, choose the least restrictive option (multivalued = true) 177 * 178 * @param node the JCR node to check 179 * @param propertyName the property name (which may or may not already exist) 180 * @return true if the property is multivalued 181 * @throws RepositoryException if repository exception occurred 182 */ 183 public static boolean isMultivaluedProperty(final Node node, final String propertyName) 184 throws RepositoryException { 185 return getDefinitionForPropertyName(node, propertyName).map(PropertyDefinition::isMultiple).orElse(true); 186 } 187 188 /** 189 * Get the property definition information (containing type and multi-value 190 * information) 191 * 192 * @param node the node to use for inferring the property definition 193 * @param propertyName the property name to retrieve a definition for 194 * @return a JCR PropertyDefinition, if available 195 * @throws javax.jcr.RepositoryException if repository exception occurred 196 */ 197 public static Optional<PropertyDefinition> getDefinitionForPropertyName(final Node node, final String propertyName) 198 throws RepositoryException { 199 LOGGER.debug("Looking for property name: {}", propertyName); 200 final Predicate<PropertyDefinition> sameName = p -> propertyName.equals(p.getName()); 201 202 final PropertyDefinition[] propDefs = node.getPrimaryNodeType().getPropertyDefinitions(); 203 final Optional<PropertyDefinition> primaryCandidate = stream(propDefs).filter(sameName).findFirst(); 204 return primaryCandidate.isPresent() ? primaryCandidate : 205 stream(node.getMixinNodeTypes()).map(NodeType::getPropertyDefinitions).flatMap(Arrays::stream) 206 .filter(sameName).findFirst(); 207 } 208 209 /** 210 * When we add certain URI properties, we also want to leave a reference node 211 * @param propertyName the property name 212 * @return property name as a reference 213 */ 214 public static String getReferencePropertyName(final String propertyName) { 215 return propertyName + REFERENCE_PROPERTY_SUFFIX; 216 } 217 218 /** 219 * Given an internal reference node property, get the original name 220 * @param refPropertyName the reference node property name 221 * @return original property name of the reference property 222 */ 223 public static String getReferencePropertyOriginalName(final String refPropertyName) { 224 final int i = refPropertyName.lastIndexOf(REFERENCE_PROPERTY_SUFFIX); 225 return i < 0 ? refPropertyName : refPropertyName.substring(0, i); 226 } 227 228 /** 229 * Check if a property definition is a reference property 230 * @param node the given node 231 * @param propertyName the property name 232 * @return whether a property definition is a reference property 233 * @throws RepositoryException if repository exception occurred 234 */ 235 public static boolean isReferenceProperty(final Node node, final String propertyName) throws RepositoryException { 236 final Optional<PropertyDefinition> propertyDefinition = getDefinitionForPropertyName(node, propertyName); 237 238 return propertyDefinition.isPresent() && 239 (propertyDefinition.get().getRequiredType() == REFERENCE 240 || propertyDefinition.get().getRequiredType() == WEAKREFERENCE); 241 } 242 243 244 /** 245 * Get the closest ancestor that current exists 246 * 247 * @param session the given session 248 * @param path the given path 249 * @return the closest ancestor that current exists 250 * @throws RepositoryException if repository exception occurred 251 */ 252 public static Node getClosestExistingAncestor(final Session session, final String path) 253 throws RepositoryException { 254 255 String potentialPath = path.startsWith("/") ? path : "/" + path; 256 while (!potentialPath.isEmpty()) { 257 if (session.nodeExists(potentialPath)) { 258 return session.getNode(potentialPath); 259 } 260 potentialPath = potentialPath.substring(0, potentialPath.lastIndexOf('/')); 261 } 262 return session.getRootNode(); 263 } 264 265}