001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.kernel.modeshape.services; 019 020import static org.fcrepo.kernel.modeshape.utils.NamespaceTools.getNamespaces; 021 022import org.fcrepo.kernel.api.FedoraSession; 023import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 024import org.fcrepo.kernel.api.exception.TombstoneException; 025import org.fcrepo.kernel.modeshape.TombstoneImpl; 026import org.modeshape.jcr.api.JcrTools; 027 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Set; 032import java.util.TreeSet; 033import java.util.stream.Collectors; 034 035import javax.jcr.Node; 036import javax.jcr.RepositoryException; 037import javax.jcr.Session; 038 039import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_PAIRTREE; 040import static org.fcrepo.kernel.modeshape.FedoraSessionImpl.getJcrSession; 041import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.getClosestExistingAncestor; 042import static org.modeshape.jcr.api.JcrConstants.NT_FOLDER; 043 044 045/** 046 * @author bbpennel 047 * @author ajs6f 048 * @since Feb 20, 2014 049 */ 050public class AbstractService { 051 private final static JcrTools jcrTools = new JcrTools(); 052 053 protected static Set<String> registeredPrefixes = null; 054 055 protected Node findOrCreateNode(final FedoraSession session, 056 final String path, 057 final String finalNodeType) throws RepositoryException { 058 059 final Session jcrSession = getJcrSession(session); 060 final String encodedPath = encodePath(path, session); 061 final Node preexistingNode = getClosestExistingAncestor(jcrSession, encodedPath); 062 063 if (TombstoneImpl.hasMixin(preexistingNode)) { 064 throw new TombstoneException(new TombstoneImpl(preexistingNode)); 065 } 066 067 final Node node = jcrTools.findOrCreateNode(jcrSession, encodedPath, NT_FOLDER, finalNodeType); 068 069 if (node.isNew()) { 070 tagHierarchyWithPairtreeMixin(preexistingNode, node); 071 } 072 073 return node; 074 } 075 076 protected Node findNode(final FedoraSession session, final String path) { 077 final Session jcrSession = getJcrSession(session); 078 final String encodedPath = encodePath(path, session); 079 try { 080 return jcrSession.getNode(encodedPath); 081 } catch (final RepositoryException e) { 082 throw new RepositoryRuntimeException(e); 083 } 084 } 085 086 /** 087 * Encode colons when they are NOT preceded by a registered prefix. 088 * 089 * @param path the path 090 * @param session a JCR session 091 * @return the encoded path 092 */ 093 public static String encodePath(final String path, final FedoraSession session) { 094 return pathCoder(true, path, session); 095 } 096 097 /** 098 * Decode colons when they are NOT preceded by a registered prefix. 099 * 100 * @param path the path 101 * @param session a JCR session 102 * @return the decoded path 103 */ 104 public static String decodePath(final String path, final FedoraSession session) { 105 return pathCoder(false, path, session); 106 } 107 108 /** 109 * Does the actual path encoding/decoding. 110 * 111 * @param encode whether to encode or decode 112 * @param path the path 113 * @param session the JCR session 114 * @return the encoded/decoded path. 115 */ 116 private static String pathCoder(final boolean encode, final String path, final FedoraSession session) { 117 final String searchString = encode ? ":" : "%3A"; 118 final String replaceString = encode ? "%3A" : ":"; 119 if (path.equals("/") || path.isEmpty() || !path.contains(searchString)) { 120 // Short circuit if the path is nothing or doesn't contain the search string 121 return path; 122 } 123 final Session jcrSession = getJcrSession(session); 124 final boolean endsWithSlash = path.endsWith("/"); 125 final List<String> pathParts = Arrays.asList(path.split("/")); 126 final List<String> newPath = new ArrayList<>(); 127 for (final String p : pathParts) { 128 if (p.contains(searchString)) { 129 final String[] prefix = p.split(searchString); 130 if (!registeredPrefixes(jcrSession).contains(prefix[0])) { 131 newPath.add(p.replace(searchString, replaceString)); 132 continue; 133 } 134 } 135 newPath.add(p); 136 } 137 return newPath.stream().collect(Collectors.joining("/", "", endsWithSlash ? "/" : "")); 138 } 139 140 /** 141 * Tag a hierarchy with {@link org.fcrepo.kernel.api.FedoraTypes#FEDORA_PAIRTREE} 142 * 143 * @param baseNode Top most ancestor that should not be tagged 144 * @param createdNode Node whose parents should be tagged up to but not including {@code baseNode} 145 * @throws RepositoryException if repository exception occurred 146 */ 147 private static void tagHierarchyWithPairtreeMixin(final Node baseNode, final Node createdNode) 148 throws RepositoryException { 149 Node parent = createdNode.getParent(); 150 151 while (parent.isNew() && !parent.equals(baseNode)) { 152 parent.addMixin(FEDORA_PAIRTREE); 153 parent = parent.getParent(); 154 } 155 } 156 157 /** test node existence at path 158 * 159 * @param session the session 160 * @param path the path 161 * @return whether T exists at the given path 162 */ 163 public boolean exists(final FedoraSession session, final String path) { 164 final Session jcrSession = getJcrSession(session); 165 final String encodedPath = encodePath(path, session); 166 try { 167 return jcrSession.nodeExists(encodedPath); 168 } catch (final RepositoryException e) { 169 throw new RepositoryRuntimeException(e); 170 } 171 } 172 173 /** 174 * Get the prefixes in the JCR NamespaceRegistry 175 * 176 * @param session current JCR Session 177 * @return Set of prefixes 178 */ 179 protected static Set<String> registeredPrefixes(final Session session) { 180 if (registeredPrefixes == null || registeredPrefixes.isEmpty()) { 181 registeredPrefixes = new TreeSet<String>(getNamespaces(session).keySet()); 182 // Add fcr: as it is not actually registered in Modeshape. 183 registeredPrefixes.add("fcr"); 184 } 185 return registeredPrefixes; 186 } 187}