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.rdf.impl;
019
020import static com.google.common.collect.Lists.newArrayList;
021import static org.apache.jena.rdf.model.ResourceFactory.createResource;
022
023import com.google.common.base.Converter;
024import com.google.common.collect.Lists;
025
026import org.fcrepo.kernel.api.models.FedoraResource;
027import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
028import org.fcrepo.kernel.api.identifiers.IdentifierConverter;
029
030import org.apache.jena.rdf.model.Resource;
031
032import org.fcrepo.kernel.modeshape.identifiers.HashConverter;
033import org.fcrepo.kernel.modeshape.identifiers.NamespaceConverter;
034import org.fcrepo.kernel.modeshape.identifiers.NodeResourceConverter;
035
036import javax.jcr.RepositoryException;
037import javax.jcr.Session;
038
039import java.util.List;
040
041/**
042 * A very simple {@link IdentifierConverter} which translates JCR paths into Fedora subjects with
043 * a configurable resource namespace (e.g., a baseURL).  When a REST API context is available for
044 * constructing URIs, org.fcrepo.http.commons.api.rdf.HttpResourceConverter should be used instead.
045 *
046 * @author barmintor
047 * @author ajs6f
048 * @author escowles
049 * @since 2015-04-24
050 */
051public class PrefixingIdentifierTranslator extends IdentifierConverter<Resource, FedoraResource> {
052
053
054    private static final NodeResourceConverter nodeResourceConverter = new NodeResourceConverter();
055
056    private final String resourceNamespace;
057    private final Session session;
058
059    /**
060     * Construct the graph with the provided resource namespace, which will translate JCR paths into
061     * URIs prefixed with that namespace.  Should only be used when a REST API context is not available
062     * for constructing URIs.
063     * @param session Session to lookup nodes
064     * @param resourceNamespace Resource namespace (i.e., base URL)
065    **/
066    public PrefixingIdentifierTranslator(final Session session, final String resourceNamespace) {
067        this.session = session;
068        this.resourceNamespace = resourceNamespace;
069        setTranslationChain();
070    }
071
072
073    protected Converter<String, String> forward = identity();
074    protected Converter<String, String> reverse = identity();
075
076    /*
077     * TODO: much of what happens with chains of translators inside these converters should be factored
078     * out into some abstract class, or post Java 8, default implementation.
079     */
080    private void setTranslationChain() {
081
082        for (final Converter<String, String> t : minimalTranslationChain) {
083            forward = forward.andThen(t);
084        }
085        for (final Converter<String, String> t : Lists.reverse(minimalTranslationChain)) {
086            reverse = reverse.andThen(t.reverse());
087        }
088    }
089
090
091    @SuppressWarnings("unchecked")
092    private static final List<Converter<String, String>> minimalTranslationChain =
093            newArrayList((Converter<String, String>) new NamespaceConverter(),
094                    (Converter<String, String>) new HashConverter()
095            );
096
097    @Override
098    protected FedoraResource doForward(final Resource subject) {
099        try {
100            if (!inDomain(subject)) {
101                throw new RepositoryRuntimeException("Subject " + subject + " is not in this repository");
102            }
103
104            return nodeResourceConverter.convert(session.getNode(asString(subject)));
105        } catch (final RepositoryException e) {
106            throw new RepositoryRuntimeException(e);
107        }
108    }
109
110    @Override
111    protected Resource doBackward(final FedoraResource resource) {
112        final String absPath = resource.getPath();
113
114        return toDomain(absPath);
115    }
116
117    @Override
118    public boolean inDomain(final Resource subject) {
119        return subject.isURIResource() && subject.getURI().startsWith(resourceNamespace);
120    }
121
122    @Override
123    public Resource toDomain(final String absPath) {
124        final String relativePath;
125
126        if (absPath.startsWith("/")) {
127            relativePath = absPath.substring(1);
128        } else {
129            relativePath = absPath;
130        }
131        return createResource(resourceNamespace + reverse.convert(relativePath));
132    }
133
134    @Override
135    public String asString(final Resource subject) {
136        if (!inDomain(subject)) {
137            return null;
138        }
139
140        final String path = subject.getURI().substring(resourceNamespace.length() - 1);
141
142        final String absPath = forward.convert(path);
143
144        if (absPath.isEmpty()) {
145            return "/";
146        }
147        return absPath;
148    }
149
150}