com.sap.cds.adapter.odata.v4.preview.CdsFioriPreviewServlet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-adapter-odata-v4 Show documentation
Show all versions of cds-adapter-odata-v4 Show documentation
OData V4 adapter for CDS Services Java
package com.sap.cds.adapter.odata.v4.preview;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import com.sap.cds.adapter.odata.v4.ODataV4IndexContentProvider;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsService;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.environment.CdsProperties.ODataV4.FioriPreview.UI5;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cds.services.utils.ODataUtils;
import com.sap.cds.services.utils.path.CdsResourcePath;
import com.sap.cds.services.utils.path.CdsServicePath;
import com.sap.cds.services.utils.path.UrlPathUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
class CdsFioriPreviewServlet extends HttpServlet {
private final static String COMPONENT_JS = "/app/Component.js";
private final static String MANIFEST_JSON = "/app/manifest.json";
private final static String FIORI_PATH = "/%s/%s";
private final static String ROUTE_TEMPLATE = """
,
{
"name": "${navProperty}Route",
"target": "${navProperty}Target",
"pattern": "${entityName}({key})/${navProperty}({key2}):?query:"
}\
""";
private final static String TARGET_TEMPLATE = """
,
"${navProperty}Target": {
"type": "Component",
"id": "${navProperty}Target",
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings": {
"entitySet": "${targetEntity}"
}
}
}\
""";
private final static String OPTIONS_TEMPLATE = """
"${navProperty}": {
"detail": {
"route": "${navProperty}Route"
}
},\
""";
private final CdsRuntime runtime;
private final List previews;
private final String ui5;
CdsFioriPreviewServlet(CdsRuntime runtime) {
this.runtime = runtime;
this.previews = findFioriPreviews(runtime);
UI5 config = runtime.getEnvironment().getCdsProperties().getOdataV4().getFioriPreview().getUi5();
this.ui5 = config.getHost() + (config.getVersion() != null ? "/" + config.getVersion() : "");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo();
FioriPreview preview = previews.stream()
.filter(p -> pathInfo.equals(p.fioriPath) || pathInfo.equals(p.fioriPath + COMPONENT_JS) || pathInfo.equals(p.fioriPath + MANIFEST_JSON))
.findFirst().orElse(null);
if (preview == null || !req.getMethod().equals("GET")) {
resp.setStatus(404);
return;
}
String page;
String contentType;
String contextPath = getServletConfig() == null ? req.getContextPath() : getServletConfig().getServletContext().getContextPath();
if (pathInfo.equals(preview.fioriPath)) {
contentType = "text/html;charset=UTF-8";
page = resource("index.html")
.replace("${ui5}", ui5)
.replace("${serviceName}", preview.serviceName)
.replace("${entityName}", preview.entityName)
.replace("${componentPath}", contextPath + CdsFioriPreviewServletFactory.BASE_PATH + preview.fioriPath + "/app");
} else if (pathInfo.equals(preview.fioriPath + MANIFEST_JSON)) {
contentType = "application/json;charset=UTF-8";
page = resource("manifest.json")
.replace("${serviceName}", preview.serviceName)
.replace("${entityName}", preview.entityName)
.replace("${servicePath}", odataServicePath(preview.servicePath, contextPath));
String navRoutes = "";
String navTargets = "";
String navOptions = "";
for (CdsElement assoc : preview.entity.associations().filter(this::isExposedAssociation).toList()) {
String navProperty = assoc.getName();
String targetEntity = ODataUtils.toODataName(assoc.getType().as(CdsAssociationType.class).getTarget().getName());
navRoutes += ROUTE_TEMPLATE
.replace("${navProperty}", navProperty)
.replace("${entityName}", preview.entityName);
navTargets += TARGET_TEMPLATE
.replace("${navProperty}", navProperty)
.replace("${targetEntity}", targetEntity);
navOptions += OPTIONS_TEMPLATE
.replace("${navProperty}", navProperty);
}
navOptions = !navOptions.isEmpty() ? navOptions.substring(0, navOptions.length() - 1) : navOptions;
page = page.replace("${navRoutes}", navRoutes)
.replace("${navTargets}", navTargets)
.replace("${navOptions}", navOptions);
} else {
contentType = "text/javascript;charset=UTF-8";
page = resource("Component.js");
}
resp.setStatus(200);
resp.setContentType(contentType);
resp.getWriter().write(page);
}
private String odataServicePath(String servicePath, String contextPath) {
String basePath = UrlPathUtil.normalizeBasePath(runtime.getEnvironment().getCdsProperties().getOdataV4().getEndpoint().getPath());
String theBasePath = contextPath + (basePath.equals("/") ? "" : basePath);
return theBasePath + "/" + servicePath;
}
private String resource(String file) {
try {
return new String(CdsFioriPreviewServlet.class.getClassLoader()
.getResourceAsStream("com.sap.cds/fiori-preview/" + file).readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new ErrorStatusException(ErrorStatuses.NOT_FOUND, e);
}
}
private boolean isExposedAssociation(CdsElement e) {
String target = e.getType().as(CdsAssociationType.class).getTarget().getQualifiedName();
return !e.getName().equals("SiblingEntity") &&
!target.endsWith(".texts") &&
!target.endsWith("_texts") &&
!target.endsWith("DraftAdministrativeData");
}
record FioriPreview(String serviceName, String entityName, CdsEntity entity, String servicePath, String fioriPath) {};
private static List findFioriPreviews(CdsRuntime runtime) {
List previews = new ArrayList<>();
Stream cdsServicePaths = CdsServicePath.servicePaths(runtime, "odata-v4");
cdsServicePaths
.filter(p -> p.getCdsDefinition() instanceof CdsService)
.forEach(s -> {
String serviceName = s.getCdsDefinition().getQualifiedName();
String servicePath = s.getPath();
s.subPaths()
.filter(e -> ODataV4IndexContentProvider.isExposedEntity(e.getCdsDefinition()))
.forEach(e -> {
String entityName = ODataUtils.toODataName(e.getPath());
previews.add(new FioriPreview(serviceName, entityName, e.getCdsDefinition().as(CdsEntity.class),
servicePath, FIORI_PATH.formatted(serviceName, entityName)));
});
});
return previews;
}
}