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