/** * Creates a new {@link CompositeServiceEntry} whose {@link Service} is bound at the specified exact path. * * @see PathMapping#ofExact(String) */ public static <I extends Request, O extends Response> CompositeServiceEntry<I, O> ofExact(String exactPath, Service<I, O> service) { return new CompositeServiceEntry<>(PathMapping.ofExact(exactPath), service); }
private static String endpointPathMapping(PathMapping pathMapping) { final Optional<String> exactPath = pathMapping.exactPath(); if (exactPath.isPresent()) { return EXACT + exactPath.get(); } final Optional<String> regex = pathMapping.regex(); if (regex.isPresent()) { return REGEX + regex.get(); } final Optional<String> prefix = pathMapping.prefix(); if (prefix.isPresent()) { return PREFIX + prefix.get(); } return getNormalizedTriePath(pathMapping); }
/** * Creates a new {@link CompositeServiceEntry} whose {@link Service} is bound at the specified path pattern. * * @see PathMapping#of(String) */ public static <I extends Request, O extends Response> CompositeServiceEntry<I, O> of(String pathPattern, Service<I, O> service) { return new CompositeServiceEntry<>(PathMapping.of(pathPattern), service); }
/** * Creates a new instance that redirects to the location constructed with the specified * {@code locationPattern}. * * @param redirectStatus the {@link HttpStatus} that the {@link Service} will return. * @param locationPattern the location pattern that is used to generate a redirect location. * * @throws IllegalArgumentException if the specified {@code locationPattern} is unsupported or invalid */ public RedirectService(HttpStatus redirectStatus, String locationPattern) { this(redirectStatus, toLocationFunction(locationPattern)); final Matcher m = PATTERN_PARAMS_START.matcher(locationPattern); if (m.find()) { paramNames = PathMapping.of(locationPattern.substring(m.start())).paramNames(); } }
@Override public ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs) { final Map<Class<?>, EntryBuilder> map = new LinkedHashMap<>(); for (ServiceConfig c : serviceConfigs) { final THttpService service = c.service().as(THttpService.class).get(); service.entries().forEach((serviceName, entry) -> { for (Class<?> iface : entry.interfaces()) { final Class<?> serviceClass = iface.getEnclosingClass(); final EntryBuilder builder = map.computeIfAbsent(serviceClass, cls -> new EntryBuilder(serviceClass)); // Add all available endpoints. Accept only the services with exact and prefix path // mappings, whose endpoint path can be determined. final PathMapping pathMapping = c.pathMapping(); final String path = pathMapping.exactPath().orElse(pathMapping.prefix().orElse(null)); if (path != null) { builder.endpoint(new EndpointInfoBuilder(c.virtualHost().hostnamePattern(), path) .fragment(serviceName) .defaultFormat(service.defaultSerializationFormat()) .availableFormats(service.allowedSerializationFormats()) .build()); } } }); } final List<Entry> entries = map.values().stream() .map(EntryBuilder::build) .collect(Collectors.toList()); return generate(entries); }
final PathMapping mapping = PathMapping.of(pattern); if ("/".equals(pathPrefix)) { return PathMapping.ofExact(concatPaths( pathPrefix, pattern.substring(EXACT.length()))); return PathMapping.ofPrefix(concatPaths( pathPrefix, pattern.substring(PREFIX.length()))); final String glob = pattern.substring(GLOB.length()); if (glob.startsWith("/")) { return PathMapping.ofGlob(concatPaths(pathPrefix, glob)); } else { // NB: We cannot use PathMapping.ofGlob(pathPrefix + "/**/" + glob) here return PathMapping.of(concatPaths(pathPrefix, pattern));
@Override public Optional<String> exactPath() { return pathStringMapping.exactPath(); }
@Override public Optional<String> prefix() { return pathStringMapping.prefix(); }
@Override public O serve(ServiceRequestContext ctx, I req) throws Exception { final PathMappingContext mappingCtx = ctx.pathMappingContext(); final PathMapped<Service<I, O>> mapped = findService(mappingCtx.overridePath(ctx.mappedPath())); if (!mapped.isPresent()) { throw HttpStatusException.of(HttpStatus.NOT_FOUND); } final Optional<String> childPrefix = mapped.mapping().prefix(); if (childPrefix.isPresent()) { final PathMapping newMapping = PathMapping.ofPrefix(ctx.pathMapping().prefix().get() + childPrefix.get().substring(1)); final ServiceRequestContext newCtx = new CompositeServiceRequestContext( ctx, newMapping, mapped.mappingResult().path()); try (SafeCloseable ignored = newCtx.push(false)) { return mapped.value().serve(newCtx, req); } } else { return mapped.value().serve(ctx, req); } }
/** * Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path} * under the specified directory prefix. It also removes the specified directory prefix from the matched * path so that {@link ServiceRequestContext#path()} does not have the specified directory prefix. * For example, when {@code pathPrefix} is {@code "/foo/"}: * <ul> * <li>{@code "/foo/"} translates to {@code "/"}</li> * <li>{@code "/foo/bar"} translates to {@code "/bar"}</li> * <li>{@code "/foo/bar/baz"} translates to {@code "/bar/baz"}</li> * </ul> * This method is a shortcut to {@link #ofPrefix(String, boolean) ofPrefix(pathPrefix, true)}. */ static PathMapping ofPrefix(String pathPrefix) { return ofPrefix(pathPrefix, true); }
/** * Creates a new {@link CompositeServiceEntry} whose {@link Service} is bound at * {@linkplain PathMapping#ofCatchAll() the catch-all path mapping}. */ public static <I extends Request, O extends Response> CompositeServiceEntry<I, O> ofCatchAll(Service<I, O> service) { return new CompositeServiceEntry<>(PathMapping.ofCatchAll(), service); }
@Override public ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs) { final Map<Class<?>, EntryBuilder> map = new LinkedHashMap<>(); for (ServiceConfig c : serviceConfigs) { final THttpService service = c.service().as(THttpService.class).get(); service.entries().forEach((serviceName, entry) -> { for (Class<?> iface : entry.interfaces()) { final Class<?> serviceClass = iface.getEnclosingClass(); final EntryBuilder builder = map.computeIfAbsent(serviceClass, cls -> new EntryBuilder(serviceClass)); // Add all available endpoints. Accept only the services with exact and prefix path // mappings, whose endpoint path can be determined. final PathMapping pathMapping = c.pathMapping(); final String path = pathMapping.exactPath().orElse(pathMapping.prefix().orElse(null)); if (path != null) { builder.endpoint(new EndpointInfo( c.virtualHost().hostnamePattern(), path, serviceName, service.defaultSerializationFormat(), service.allowedSerializationFormats())); } } }); } final List<Entry> entries = map.values().stream() .map(EntryBuilder::build) .collect(Collectors.toList()); return generate(entries); }
/** * Returns whether the given {@code path} and {@code query} should be cached if the service's result is * successful. By default, exact path mappings with no input query are cached. */ default boolean shouldCachePath(String path, @Nullable String query, PathMapping pathMapping) { return pathMapping.exactPath().isPresent() && query == null; } }
@Nullable @VisibleForTesting static EndpointInfo endpointInfo(PathMapping pathMapping, String hostnamePattern) { final String endpointPathMapping = endpointPathMapping(pathMapping); if (isNullOrEmpty(endpointPathMapping)) { return null; } final EndpointInfoBuilder builder = new EndpointInfoBuilder(hostnamePattern, endpointPathMapping); if (endpointPathMapping.startsWith(REGEX) && pathMapping.prefix().isPresent()) { // PrefixAddingPathMapping builder.regexPathPrefix(PREFIX + pathMapping.prefix().get()); } builder.availableMimeTypes(availableMimeTypes(pathMapping)); return builder.build(); }
/** * Creates a new {@link CompositeServiceEntry} whose {@link Service} is bound under the specified * directory. * * @see PathMapping#ofPrefix(String) */ public static <I extends Request, O extends Response> CompositeServiceEntry<I, O> ofPrefix(String pathPrefix, Service<I, O> service) { return new CompositeServiceEntry<>(PathMapping.ofPrefix(pathPrefix), service); }
/** * Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path} * under the specified directory prefix. When {@code stripPrefix} is {@code true}, it also removes the * specified directory prefix from the matched path so that {@link ServiceRequestContext#path()} * does not have the specified directory prefix. For example, when {@code pathPrefix} is {@code "/foo/"}: * <ul> * <li>{@code "/foo/"} translates to {@code "/"}</li> * <li>{@code "/foo/bar"} translates to {@code "/bar"}</li> * <li>{@code "/foo/bar/baz"} translates to {@code "/bar/baz"}</li> * </ul> */ static PathMapping ofPrefix(String pathPrefix, boolean stripPrefix) { requireNonNull(pathPrefix, "pathPrefix"); if ("/".equals(pathPrefix)) { // Every path starts with '/'. return ofCatchAll(); } return new PrefixPathMapping(pathPrefix, stripPrefix); }
@Override public ServiceSpecification generateSpecification(Set<ServiceConfig> serviceConfigs) { final Map<Class<?>, EntryBuilder> map = new LinkedHashMap<>(); for (ServiceConfig c : serviceConfigs) { final THttpService service = c.service().as(THttpService.class).get(); service.entries().forEach((serviceName, entry) -> { for (Class<?> iface : entry.interfaces()) { final Class<?> serviceClass = iface.getEnclosingClass(); final EntryBuilder builder = map.computeIfAbsent(serviceClass, cls -> new EntryBuilder(serviceClass)); // Add all available endpoints. Accept only the services with exact and prefix path // mappings, whose endpoint path can be determined. final PathMapping pathMapping = c.pathMapping(); final String path = pathMapping.exactPath().orElse(pathMapping.prefix().orElse(null)); if (path != null) { builder.endpoint(new EndpointInfoBuilder(c.virtualHost().hostnamePattern(), path) .fragment(serviceName) .defaultFormat(service.defaultSerializationFormat()) .availableFormats(service.allowedSerializationFormats()) .build()); } } }); } final List<Entry> entries = map.values().stream() .map(EntryBuilder::build) .collect(Collectors.toList()); return generate(entries); }
/** * Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path} with * the specified glob expression, where {@code "*"} matches a path component non-recursively and * {@code "**"} matches path components recursively. */ static PathMapping ofGlob(String glob) { requireNonNull(glob, "glob"); if (glob.startsWith("/") && !glob.contains("*")) { // Does not have a pattern matcher. return ofExact(glob); } return new GlobPathMapping(glob); }
/** * Sets the path pattern of the service. */ public HttpServiceRegistrationBean setPathPattern(@NotNull String pathPattern) { return setPathMapping(PathMapping.of(pathPattern)); } }