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.http.commons.api.rdf; 019 020import static java.nio.charset.StandardCharsets.UTF_8; 021import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX; 022import static org.slf4j.LoggerFactory.getLogger; 023 024import java.net.URLDecoder; 025import java.util.HashMap; 026import java.util.Map; 027 028import javax.ws.rs.core.UriBuilder; 029 030import org.fcrepo.kernel.api.identifiers.FedoraId; 031 032import org.glassfish.jersey.uri.UriTemplate; 033import org.slf4j.Logger; 034 035/** 036 * Convert between HTTP URIs (LDP paths) and internal Fedora ID using a 037 * JAX-RS UriBuilder to mediate the URI translation. 038 * 039 * @author whikloj 040 * @since 2019-09-26 041 */ 042public class HttpIdentifierConverter { 043 044 private static final Logger LOGGER = getLogger(HttpIdentifierConverter.class); 045 046 private final UriBuilder uriBuilder; 047 048 private final UriTemplate uriTemplate; 049 050 private static String trimTrailingSlashes(final String string) { 051 return string.replaceAll("/+$", ""); 052 } 053 054 /** 055 * Create a new identifier converter with the given URI template. 056 * @param uriBuilder the uri builder 057 */ 058 public HttpIdentifierConverter(final UriBuilder uriBuilder) { 059 this.uriBuilder = uriBuilder; 060 this.uriTemplate = new UriTemplate(uriBuilder.toTemplate()); 061 } 062 063 /** 064 * Convert an external URI to an internal ID. 065 * 066 * @param httpUri the external URI. 067 * @return the internal identifier. 068 */ 069 public String toInternalId(final String httpUri) { 070 return toInternalId(httpUri, false); 071 } 072 073 /** 074 * Convert an external URI to an internal ID. 075 * 076 * @param httpUri the external URI. 077 * @param encoded whether the internal ID is encoded or not. 078 * @return the internal identifier. 079 */ 080 public String toInternalId(final String httpUri, final boolean encoded) { 081 LOGGER.trace("Translating http URI {} to Fedora ID with encoded set to {}", httpUri, encoded); 082 083 final String path = getPath(httpUri); 084 if (path != null) { 085 final String decodedPath; 086 if (!encoded) { 087 decodedPath = URLDecoder.decode(path, UTF_8); 088 } else { 089 decodedPath = path; 090 } 091 final String fedoraId = trimTrailingSlashes(decodedPath); 092 093 return FEDORA_ID_PREFIX + fedoraId; 094 } 095 throw new IllegalArgumentException("Cannot translate NULL path"); 096 } 097 098 /** 099 * Test if the provided external URI is in the domain of this repository. 100 * 101 * If it is not in the domain we can't convert it. 102 * 103 * @param httpUri the external URI. 104 * @return true if it is in domain. 105 */ 106 public boolean inExternalDomain(final String httpUri) { 107 LOGGER.trace("Checking if http URI {} is in domain", httpUri); 108 return getPath(httpUri) != null; 109 } 110 111 /** 112 * Make a URI into an absolute URI (if relative), an internal encoded ID (if in repository domain) or leave it 113 * alone. 114 * @param httpUri the URI 115 * @return an absolute URI, the original URI or an internal ID. 116 */ 117 public String translateUri(final String httpUri) { 118 if (inExternalDomain(httpUri)) { 119 return toInternalId(httpUri, true); 120 } else if (httpUri.startsWith("/")) { 121 // Is a relative URI. 122 // Build a fake URI using the hostname so we can resolve against it. 123 final var uri = uriBuilder.build("placeholder"); 124 return uri.resolve(httpUri).toString(); 125 } 126 return httpUri; 127 } 128 129 /** 130 * Convert an internal identifier to an external URI. 131 * 132 * @param fedoraId the internal identifier. 133 * @return the external URI. 134 */ 135 public String toExternalId(final String fedoraId) { 136 LOGGER.trace("Translating Fedora ID {} to Http URI", fedoraId); 137 if (inInternalDomain(fedoraId)) { 138 // If it starts with our prefix, strip the prefix and any leading slashes and use it as the path 139 // part of the URI. 140 final String path = fedoraId.substring(FEDORA_ID_PREFIX.length()).replaceFirst("/", ""); 141 return buildUri(path); 142 } 143 throw new IllegalArgumentException("Cannot translate IDs without our prefix"); 144 } 145 146 /** 147 * Check if the provided internal identifier is in the domain of the repository. 148 * 149 * If it is not in the domain we can't convert it. 150 * 151 * @param fedoraId the internal identifier. 152 * @return true if it is in domain. 153 */ 154 public boolean inInternalDomain(final String fedoraId) { 155 LOGGER.trace("Checking if fedora ID {} is in domain", fedoraId); 156 return (fedoraId.startsWith(FEDORA_ID_PREFIX)); 157 } 158 159 /** 160 * Return a UriBuilder for the current template. 161 * 162 * @return the uri builder. 163 */ 164 private UriBuilder uriBuilder() { 165 return UriBuilder.fromUri(uriBuilder.toTemplate()); 166 } 167 168 /** 169 * Convert a path to a full url using the UriBuilder template. 170 * @param path the external path. 171 * @return the full url. 172 */ 173 public String toDomain(final String path) { 174 175 final String realPath; 176 if (path == null) { 177 realPath = ""; 178 } else if (path.startsWith("/")) { 179 realPath = path.substring(1); 180 } else { 181 realPath = path; 182 } 183 184 return buildUri(realPath); 185 } 186 187 /** 188 * Function to convert from the external path of a URI to an internal FedoraId. 189 * @param externalPath the path part of the external URI. 190 * @return the FedoraId. 191 */ 192 public FedoraId pathToInternalId(final String externalPath) { 193 return FedoraId.create(externalPath); 194 } 195 196 /** 197 * Utility to build a URL. 198 * @param path the path from the internal Id. 199 * @return an external URI. 200 */ 201 private String buildUri(final String path) { 202 final UriBuilder uri = uriBuilder(); 203 if (path.contains("#")) { 204 final String[] split = path.split("#", 2); 205 uri.resolveTemplateFromEncoded("path", split[0]); 206 uri.fragment(split[1]); 207 } else { 208 uri.resolveTemplateFromEncoded("path", path); 209 } 210 return uri.build().toString(); 211 } 212 213 /** 214 * Split the path off the URI. 215 * 216 * @param httpUri the incoming URI. 217 * @return the path of the URI. 218 */ 219 private String getPath(final String httpUri) { 220 final Map<String, String> values = new HashMap<>(); 221 222 if (uriTemplate.match(httpUri, values) && values.containsKey("path")) { 223 return "/" + values.get("path"); 224 } else if (isRootWithoutTrailingSlash(httpUri)) { 225 return "/"; 226 } 227 return null; 228 } 229 230 /** 231 * Test if the URI is the root but missing the trailing slash 232 * 233 * @param httpUri the incoming URI. 234 * @return whether or not it is the root minus trailing slash 235 */ 236 private boolean isRootWithoutTrailingSlash(final String httpUri) { 237 final Map<String, String> values = new HashMap<>(); 238 239 return uriTemplate.match(httpUri + "/", values) && values.containsKey("path") && 240 values.get("path").isEmpty(); 241 } 242 243}