com.sap.cds.adapter.ServletUrlResourcePaths Maven / Gradle / Ivy
/**************************************************************************
* (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.adapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.runtime.ExtendedServiceLoader;
/**
* A utility to iterate the public {@link UrlResourcePath}s of Servlets created by {@link ServletAdapterFactory}
* according to the passed {@link CdsRuntime}.
*
* Define an instance of {@link UrlResourcePathVisitor} to take further action on public endpoints.
*/
public class ServletUrlResourcePaths {
private List servletPaths = new ArrayList<>();
/**
* Create an instance based on the underlying {@link CdsRuntime}.
*
* @param runtime the {@link CdsRuntime}.
*/
public ServletUrlResourcePaths(CdsRuntime runtime) {
Objects.requireNonNull(runtime, "runtime must not be null");
// we need to touch the endpoints of all servlet-based adapters
Iterator factoryIterator = ExtendedServiceLoader.loadAll(AdapterFactory.class, runtime);
factoryIterator.forEachRemaining(factory -> {
if (factory instanceof ServletAdapterFactory servletFactory && servletFactory.isEnabled()) {
UrlResourcePath servletPath = servletFactory.getServletPath();
servletPaths.add(servletPath);
}
});
Collections.sort(servletPaths, (p1, p2) -> {
// more segments need to be handled earlier
int comparison = -1 * Integer.compare(countSegments(p1), countSegments(p2));
if(comparison == 0) {
// if segments are the same, check if one path is a substring of another
// the shorter path wins in that case, e.g. / vs /**
if(p1.getPath().startsWith(p2.getPath())) {
return 1;
} else if (p2.getPath().startsWith(p1.getPath())) {
return -1;
}
}
return comparison;
});
}
/**
* Returns the base paths found in the model.
*
* @return a {@link Stream} of {@link UrlResourcePath} defining the base endpoints.
*/
public Stream getBasePaths() {
return servletPaths.stream();
}
/**
* The interface of a visitor
*/
public interface UrlResourcePathVisitor {
/**
* Called when a public {@link UrlResourcePath} is found.
* @param publicPath The public {@link UrlResourcePath}
*/
void foundPublicPath(UrlResourcePath publicPath);
/**
* Called when a {@link UrlResourcePath} is found with a public event.
* @param path The {@link UrlResourcePath}
* @param publicEvents The public events on the given path
*/
void foundPublicEvents(UrlResourcePath path, Stream publicEvents);
}
/**
* Starts enumerating all cds servlet paths recursively (depth-first order).
* @param visitor a visitor being notified during iteration
*/
public void visit(UrlResourcePathVisitor visitor) {
Objects.requireNonNull(visitor, "visitor is required to handle public endpoints");
servletPaths.forEach(p -> visitServletPath(p, visitor));
}
private void visitServletPath(UrlResourcePath servletPath, UrlResourcePathVisitor visitor) {
// first decide each sub path recursively
servletPath.subPaths().forEach(subPath -> {
visitServletPath(subPath, visitor);
});
if (servletPath.isPublic()) {
visitor.foundPublicPath(servletPath);
} else {
// some http methods might still be public even if the path itself is closed
visitor.foundPublicEvents(servletPath, servletPath.publicEvents());
}
}
private int countSegments(UrlResourcePath path) {
int count = 0;
for(char c : path.getPath().toCharArray()) {
if(c == '/') {
++count;
}
}
return count;
}
}