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}