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 = 127 new Predicate<Property>() { 128 129 @Override 130 public boolean apply(final Property p) { 131 try { 132 if (!p.getDefinition().isProtected()) { 133 return false; 134 } else if (p.getParent().isNodeType(FROZEN_NODE)) { 135 // everything on a frozen node is protected 136 // but we wish to display it anyway and there's 137 // another mechanism in place to make clear that 138 // things cannot be edited. 139 return false; 140 } else { 141 final String name = p.getName(); 142 for (String exposedName : EXPOSED_PROTECTED_JCR_TYPES) { 143 if (name.equals(exposedName)) { 144 return false; 145 } 146 } 147 return true; 148 } 149 } catch (final RepositoryException e) { 150 throw new RepositoryRuntimeException(e); 151 } 152 } 153 }; 154 155 /** 156 * Check if a node is "internal" and should not be exposed e.g. via the REST 157 * API 158 */ 159 public static Predicate<Node> isInternalNode = new Predicate<Node>() { 160 161 @Override 162 public boolean apply(final Node n) { 163 checkNotNull(n, "null is neither internal nor not internal!"); 164 try { 165 return n.isNodeType("mode:system"); 166 } catch (final RepositoryException e) { 167 throw new RepositoryRuntimeException(e); 168 } 169 } 170 }; 171 172 /** 173 * Get the JCR property type ID for a given property name. If unsure, mark 174 * it as UNDEFINED. 175 * 176 * @param node the JCR node to add the property on 177 * @param propertyName the property name 178 * @return a PropertyType value 179 * @throws RepositoryException 180 */ 181 public static int getPropertyType(final Node node, final String propertyName) 182 throws RepositoryException { 183 LOGGER.debug("Getting type of property: {} from node: {}", 184 propertyName, node); 185 final PropertyDefinition def = 186 getDefinitionForPropertyName(node, propertyName); 187 188 if (def == null) { 189 return UNDEFINED; 190 } 191 192 return def.getRequiredType(); 193 } 194 195 /** 196 * Determine if a given JCR property name is single- or multi- valued. 197 * If unsure, choose the least restrictive 198 * option (multivalued) 199 * 200 * @param node the JCR node to check 201 * @param propertyName the property name 202 * (which may or may not already exist) 203 * @return true if the property is (or could be) multivalued 204 * @throws RepositoryException 205 */ 206 public static boolean isMultivaluedProperty(final Node node, 207 final String propertyName) 208 throws RepositoryException { 209 final PropertyDefinition def = 210 getDefinitionForPropertyName(node, propertyName); 211 212 if (def == null) { 213 return true; 214 } 215 216 return def.isMultiple(); 217 } 218 219 /** 220 * Get the property definition information (containing type and multi-value 221 * information) 222 * 223 * @param node the node to use for inferring the property definition 224 * @param propertyName the property name to retrieve a definition for 225 * @return a JCR PropertyDefinition, if available, or null 226 * @throws javax.jcr.RepositoryException 227 */ 228 public static PropertyDefinition getDefinitionForPropertyName(final Node node, 229 final String propertyName) 230 throws RepositoryException { 231 232 final NodeType primaryNodeType = node.getPrimaryNodeType(); 233 final PropertyDefinition[] propertyDefinitions = primaryNodeType.getPropertyDefinitions(); 234 LOGGER.debug("Looking for property name: {}", propertyName); 235 for (final PropertyDefinition p : propertyDefinitions) { 236 LOGGER.debug("Checking property: {}", p.getName()); 237 if (p.getName().equals(propertyName)) { 238 return p; 239 } 240 } 241 242 for (final NodeType nodeType : node.getMixinNodeTypes()) { 243 for (final PropertyDefinition p : nodeType.getPropertyDefinitions()) { 244 if (p.getName().equals(propertyName)) { 245 return p; 246 } 247 } 248 } 249 return null; 250 } 251 252 /** 253 * When we add certain URI properties, we also want to leave a reference node 254 * @param propertyName 255 * @return property name as a reference 256 */ 257 public static String getReferencePropertyName(final String propertyName) { 258 return propertyName + REFERENCE_PROPERTY_SUFFIX; 259 } 260 261 /** 262 * Given an internal reference node property, get the original name 263 * @param refPropertyName 264 * @return original property name of the reference property 265 */ 266 public static String getReferencePropertyOriginalName(final String refPropertyName) { 267 final int i = refPropertyName.lastIndexOf(REFERENCE_PROPERTY_SUFFIX); 268 269 if (i < 0) { 270 return refPropertyName; 271 } 272 return refPropertyName.substring(0, i); 273 } 274 275 /** 276 * Check if a property definition is a reference property 277 * @param node 278 * @param propertyName 279 * @return 280 * @throws RepositoryException 281 */ 282 public static boolean isReferenceProperty(final Node node, final String propertyName) throws RepositoryException { 283 final PropertyDefinition propertyDefinition = getDefinitionForPropertyName(node, propertyName); 284 285 return propertyDefinition != null && 286 (propertyDefinition.getRequiredType() == REFERENCE 287 || propertyDefinition.getRequiredType() == WEAKREFERENCE); 288 } 289 290 291 /** 292 * Get the closest ancestor that current exists 293 * 294 * @param session 295 * @param path 296 * @return 297 * @throws RepositoryException 298 */ 299 public static Node getClosestExistingAncestor(final Session session, 300 final String path) throws RepositoryException { 301 final String[] pathSegments = path.replaceAll("^/+", "").replaceAll("/+$", "").split("/"); 302 303 final StringBuilder existingAncestorPath = new StringBuilder(path.length()); 304 existingAncestorPath.append("/"); 305 306 final int len = pathSegments.length; 307 for (int i = 0; i != len; ++i) { 308 final String pathSegment = pathSegments[i]; 309 310 if (session.nodeExists(existingAncestorPath.toString() + pathSegment)) { 311 // Add to existingAncestorPath ... 312 existingAncestorPath.append(pathSegment); 313 if (i != (len - 1)) { 314 existingAncestorPath.append("/"); 315 } 316 } else { 317 if (i != 0) { 318 existingAncestorPath.deleteCharAt(existingAncestorPath.length() - 1); 319 } 320 break; 321 } 322 323 } 324 325 return session.getNode(existingAncestorPath.toString()); 326 } 327 328}