001/** 002 * Copyright 2014 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 Fedora object. 054 */ 055 public static Predicate<Node> isContainer = 056 new AnyTypesPredicate(FEDORA_CONTAINER); 057 058 /** 059 * Predicate for determining whether this {@link Node} is a Fedora 060 * datastream. 061 */ 062 public static Predicate<Node> isNonRdfSourceDescription = 063 new AnyTypesPredicate(FEDORA_NON_RDF_SOURCE_DESCRIPTION); 064 065 066 /** 067 * Predicate for determining whether this {@link Node} is a Fedora 068 * binary. 069 */ 070 public static Predicate<Node> isFedoraBinary = 071 new AnyTypesPredicate(FEDORA_BINARY); 072 073 /** 074 * Predicate for determining whether this {@link FedoraResource} has a frozen node 075 */ 076 public static Predicate<FedoraResource> isFrozenNode = 077 new Predicate<FedoraResource>() { 078 079 @Override 080 public boolean apply(final FedoraResource f) { 081 return f.hasType(FROZEN_NODE) || f.getPath().contains(JCR_FROZEN_NODE); 082 } 083 }; 084 085 /** 086 * Predicate for determining whether this {@link Node} is a Fedora 087 * binary. 088 */ 089 public static Predicate<Node> isBlankNode = 090 new AnyTypesPredicate(FEDORA_BLANKNODE); 091 092 /** 093 * Check if a property is a reference property. 094 */ 095 public static Predicate<Property> isInternalReferenceProperty = 096 new Predicate<Property>() { 097 098 @Override 099 public boolean apply(final Property p) { 100 try { 101 return (p.getType() == REFERENCE || p.getType() == WEAKREFERENCE) 102 && p.getName().endsWith(REFERENCE_PROPERTY_SUFFIX); 103 } catch (final RepositoryException e) { 104 throw new RepositoryRuntimeException(e); 105 } 106 } 107 }; 108 109 /** 110 * Check whether a property is an internal property that should be suppressed 111 * from external output. 112 */ 113 public static Predicate<Property> isInternalProperty = 114 new Predicate<Property>() { 115 116 @Override 117 public boolean apply(final Property p) { 118 return JcrPropertyFunctions.isBinaryContentProperty.apply(p) 119 || isProtectedAndShouldBeHidden.apply(p); 120 } 121 }; 122 123 /** 124 * Check whether a property is protected (ie, cannot be modified directly) but 125 * is not one we've explicitly chosen to include. 126 */ 127 public static Predicate<Property> isProtectedAndShouldBeHidden = 128 new 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 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 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 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 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 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 279 * @param propertyName 280 * @return 281 * @throws RepositoryException 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 296 * @param path 297 * @return 298 * @throws RepositoryException 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}