Closed nollymar closed 1 month ago
Future Time Machine can be handled (enabled and disabled) using session attributes. These are the attributes:
req.getSession().setAttribute("tm_host", host);
req.getSession().setAttribute("tm_date", dateInMillisecondsAsString);
req.getSession().setAttribute("tm_lang", lanaguageId);
req.getSession().setAttribute("dotcache", "refresh");
I recommend adding query parameters to the PageResource
methods that need to use Time Machine. For example, to retrieve the Time Machine date to use. If these methods in the PageResource
detect that Time Machine needs to be used, the session attributes should be set.
[!IMPORTANT]
it is very important to clean up these attributes after each use to avoid permanently affecting the session:
request.getSession(false).removeAttribute("tm_host");
request.getSession(false).removeAttribute("tm_date");
request.getSession(false).removeAttribute("tm_lang");
request.getSession(false).removeAttribute("dotcache");
Here's a very simple proof-of-concept example. Please note that this is not production-ready code and should not be emulated directly. It's intended only to demonstrate the concept.
Using this basic implementation, I was able to add Time Machine support to the /api/v1/page/render/
and /api/v1/page/json/
methods.
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java
--- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java
@@ -1,22 +1,24 @@
package com.dotcms.rest.api.v1.page;
@Path("/v1/page")
@Tag(name = "Page",
description = "Endpoints that operate on pages",
externalDocs = @ExternalDocumentation(description = "Additional Page API information",
url = "https://www.dotcms.com/docs/latest/page-rest-api-layout-as-a-service-laas"))
public class PageResource {
@@ -321,40 +320,52 @@ public class PageResource {
* @param deviceInode The {@link java.lang.String}'s inode to render the page. This is used
* to render the page with specific width and height dimensions.
* @param asJson If only the HTML PAge's metadata must be returned in the JSON
* response, set this to {@code true}. Otherwise, if the rendered
* Containers and page content must be returned as well, set it to
* {@code false}.
*
* @return The HTML Page's metadata -- or the associated Vanity URL data -- in JSON format.
*
* @throws DotDataException An error occurred when accessing information in the database.
* @throws DotSecurityException The currently logged-in user does not have the necessary
* permissions to call this action.
*/
private Response getPageRender(final HttpServletRequest originalRequest,
final HttpServletRequest request,
final HttpServletResponse response, final User user,
final String uri, final String languageId,
final String modeParam, final String deviceInode,
final boolean asJson) throws DotDataException,
DotSecurityException {
+
+ if (request.getParameter("timeMachine") != null) {
+ long twentyDaysFromNowMillis = Instant.now().plus(20, ChronoUnit.DAYS).toEpochMilli();
+ request.getSession(false).setAttribute("tm_host",
+ APILocator.getHostAPI()
+ .find(APILocator.getHostAPI().findDefaultHost(user, false), user,
+ false));
+ request.getSession(false).setAttribute("tm_date", String.valueOf(twentyDaysFromNowMillis));
+ request.getSession(false).setAttribute("tm_lang", languageId);
+ request.getSession(false).setAttribute("dotcache", "refresh");
+ }
+
String resolvedUri = uri;
final Optional<CachedVanityUrl> cachedVanityUrlOpt =
this.pageResourceHelper.resolveVanityUrlIfPresent(originalRequest, uri, languageId);
if (cachedVanityUrlOpt.isPresent()) {
response.setHeader(VanityUrlAPI.VANITY_URL_RESPONSE_HEADER, cachedVanityUrlOpt.get().vanityUrlId);
if (cachedVanityUrlOpt.get().isTemporaryRedirect() || cachedVanityUrlOpt.get().isPermanentRedirect()) {
Logger.debug(this, () -> String.format("Incoming Vanity URL is a %d Redirect",
cachedVanityUrlOpt.get().response));
final EmptyPageView emptyPageView =
new EmptyPageView.Builder().vanityUrl(cachedVanityUrlOpt.get()).build();
return Response.ok(new ResponseEntityView<>(emptyPageView)).build();
} else {
final VanityUrlResult vanityUrlResult = cachedVanityUrlOpt.get().handle(uri);
resolvedUri = vanityUrlResult.getRewrite();
Logger.debug(this, () -> String.format("Incoming Vanity URL resolved to URI: %s",
vanityUrlResult.getRewrite()));
}
}
final PageMode mode = modeParam != null
? PageMode.get(modeParam)
@@ -367,50 +378,58 @@ public class PageResource {
}
PageView pageRendered;
final PageContextBuilder pageContextBuilder = PageContextBuilder.builder()
.setUser(user)
.setPageUri(resolvedUri)
.setPageMode(mode);
cachedVanityUrlOpt.ifPresent(cachedVanityUrl
-> pageContextBuilder.setVanityUrl(new VanityURLView.Builder().vanityUrl(cachedVanityUrl).build()));
if (asJson) {
pageRendered = this.htmlPageAssetRenderedAPI.getPageMetadata(
pageContextBuilder
.setParseJSON(true)
.build(),
request, response
);
} else {
final HttpSession session = request.getSession(false);
if (null != session) {
// Time Machine-Date affects the logic on the VTLs that conform parts of the
// rendered pages. So, we better get rid of it
- session.removeAttribute("tm_date");
+ if (request.getParameter("timeMachine") == null) {
+ session.removeAttribute("tm_date");
+ }
}
pageRendered = this.htmlPageAssetRenderedAPI.getPageRendered(
pageContextBuilder.build(), request, response
);
}
final Host site = APILocator.getHostAPI().find(pageRendered.getPage().getHost(), user,
PageMode.get(request).respectAnonPerms);
request.setAttribute(WebKeys.CURRENT_HOST, site);
request.getSession().setAttribute(WebKeys.CURRENT_HOST, site);
+
+ request.getSession(false).removeAttribute("tm_host");
+ request.getSession(false).removeAttribute("tm_date");
+ request.getSession(false).removeAttribute("tm_lang");
+ request.getSession(false).removeAttribute("dotcache");
+
return Response.ok(new ResponseEntityView<>(pageRendered)).build();
}
After implementing the sample code and adding some test blog posts with future publish dates, I was able to successfully test the Time Machine functionality using the following URLs:
https://local.dotcms.site:8443/api/v1/page/render/blog?language_id=1&timeMachine=true&mode=LIVE
https://local.dotcms.site:8443/api/v1/page/render/blog?language_id=1&timeMachine=true&mode=EDIT_MODE
https://local.dotcms.site:8443/api/v1/page/render/blog?language_id=1&mode=LIVE
https://local.dotcms.site:8443/api/v1/page/render/blog?language_id=1&mode=EDIT_MODE
https://local.dotcms.site:8443/api/v1/page/json/blog?language_id=1&timeMachine=true&mode=LIVE
https://local.dotcms.site:8443/api/v1/page/json/blog?language_id=1&timeMachine=true&mode=EDIT_MODE
https://local.dotcms.site:8443/api/v1/page/json/blog?language_id=1&mode=LIVE
https://local.dotcms.site:8443/api/v1/page/json/blog?language_id=1&mode=EDIT_MODE
Parent Issue
29574
Task
As a developer, I want to identify all the changes needed in the PageResource endpoint, so that it can support the "Future Time Machine" feature for UVE, allowing pages to be pulled based on a specific date.
Proposed Objective
Same as Parent Issue
Proposed Priority
Same as Parent Issue
Acceptance Criteria
Given the current PageResource implementation, when the spike is completed, then a list of actionable cards (tasks or user stories) required to implement the feature is produced.
Ensure that any proposed changes maintain backward compatibility with existing API consumers.
External Links... Slack Conversations, Support Tickets, Figma Designs, etc.
No response
Assumptions & Initiation Needs
No response
Quality Assurance Notes & Workarounds
No response
Sub-Tasks & Estimates
No response