Repository navigation
feat(storage): serve renditions, compiled CSS and templates through S3 asset storage #37776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: s3-stack/5-webdav-temp
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,6 +62,20 @@ public DotLibSassCompiler(final Host host, final String uri, final boolean live, | |
| this.sourceMap = sourceMap; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles the SCSS or Sass file into {@link #getOutput()}. | ||
| * | ||
| * <p>With S3 asset storage on, a compiled CSS output stored under a key that covers the source | ||
| * bytes, site, mode, options and build is reused, locally or restored from S3, without running | ||
| * Dart Sass. A new output is written locally and uploaded. If storing it fails, the compiled | ||
| * CSS is still returned and a warning is logged. A cache lease is held only while the sources | ||
| * are copied and while a stored output is read, not while Dart Sass runs. | ||
| * | ||
| * @throws DotSecurityException if a source cannot be read by the system user | ||
| * @throws DotStateException if the dotCMS state is inconsistent | ||
| * @throws DotDataException if a source or its version information cannot be loaded | ||
| * @throws IOException if the compile directory cannot be prepared | ||
| */ | ||
| @Override | ||
| public void compile() throws DotSecurityException, DotStateException, DotDataException, IOException { | ||
|
|
||
|
|
@@ -88,7 +102,9 @@ public void compile() throws DotSecurityException, DotStateException, DotDataExc | |
| } | ||
|
|
||
| final FileAsset mainFile = APILocator.getFileAssetAPI() | ||
| .fromContentlet(APILocator.getContentletAPI().find(info.get().getWorkingInode(), | ||
| .fromContentlet(APILocator.getContentletAPI().find( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ [P3] DotLibSassCompiler.java:107 null live inode breaks live compile when live version is absent Current code: .fromContentlet(APILocator.getContentletAPI().find(
com.dotcms.storage.AssetStorageFeature.isEnabled() && live
? info.get().getLiveInode() : info.get().getWorkingInode(),Problem: With flag on and live, a null Fix: .fromContentlet(APILocator.getContentletAPI().find(
com.dotcms.storage.AssetStorageFeature.isEnabled() && live
&& info.get().getLiveInode() != null
? info.get().getLiveInode() : info.get().getWorkingInode(),Assumption: a live asset is normally confirmed before compiling, so this is defensive. What to verify: that a published-then-unpublished SCSS file cannot reach this line in live mode. |
||
| com.dotcms.storage.AssetStorageFeature.isEnabled() && live | ||
| ? info.get().getLiveInode() : info.get().getWorkingInode(), | ||
| APILocator.systemUser(), true)); | ||
|
|
||
| // build directories to build scss | ||
|
|
@@ -102,10 +118,31 @@ public void compile() throws DotSecurityException, DotStateException, DotDataExc | |
| } | ||
| final File compileDestinationFile = new File(compileTargetFile.getAbsoluteFile() + ".css"); | ||
| final CompilerOptions options = new CompilerOptions.Builder().sourceMap(this.sourceMap).build(); | ||
| // Source maps remain request-private and uncached, as in the servlet's existing contract. | ||
| final File storedCSS = com.dotcms.storage.AssetStorageFeature.isEnabled() && !sourceMap | ||
| ? compiledCacheFile(mainFile.getInode(), options) : null; | ||
| if (storedCSS != null) { | ||
| try (var lease = APILocator.getBinaryAssetStorageAPI().acquireCacheLease()) { | ||
| final File cached = APILocator.getBinaryAssetStorageAPI().getGeneratedFile(storedCSS); | ||
| if (cached != null && (req == null || req.getParameter("recompile") == null)) { | ||
| this.output = java.nio.file.Files.readAllBytes(cached.toPath()); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| final DartSassCompiler compiler = new DartSassCompiler(options, compileTargetFile, compileDestinationFile); | ||
| final Optional<String> out = compiler.compile(); | ||
| handleOutput(compiler.terminalOutput()); | ||
| this.output = out.isPresent() ? out.get().getBytes() : null; | ||
| if (storedCSS != null && this.output != null && compiler.terminalOutput().exitValue() == 0) { | ||
| try { | ||
| storeCompiledOutput(storedCSS); | ||
| } catch (final Exception e) { | ||
| // Availability first: the CSS is compiled, so a storage failure must not fail the request. | ||
| Logger.warnAndDebug(DotLibSassCompiler.class, "Unable to store compiled CSS for " + inputHost | ||
| + ":" + inputURI + "; serving the compiled output: " + e.getMessage(), e); | ||
| } | ||
| } | ||
| } catch (final Exception ex) { | ||
| final String errorMsg = String.format("Unable to compile SASS code in %s:%s [ live:%s ]: %s", inputHost, | ||
| inputURI, inputLive, ex.getMessage()); | ||
|
|
@@ -119,6 +156,46 @@ public void compile() throws DotSecurityException, DotStateException, DotDataExc | |
| } | ||
| } | ||
|
|
||
| private File compiledCacheFile(final String inode, final CompilerOptions options) { | ||
| final StringBuilder identity = new StringBuilder("css-v1:") | ||
| .append(com.liferay.portal.util.ReleaseInfo.getVersion()).append(':') | ||
| .append(com.liferay.portal.util.ReleaseInfo.getBuildNumber()).append(':') | ||
| .append(inputHost.getIdentifier()).append(':').append(inputURI).append(':') | ||
| .append(live).append(':').append(options.generate()); | ||
| sourceDigests.forEach((path, digest) -> identity.append(':').append(path.length()) | ||
| .append(':').append(path).append(':').append(digest)); | ||
| final String hash = org.apache.commons.codec.digest.DigestUtils.md5Hex(identity.toString()); | ||
| return new File(com.dotmarketing.util.ConfigUtils.getDotGeneratedPath(), | ||
| inode.charAt(0) + "/" + inode.charAt(1) + "/" + inode + "/dotGenerated_css_" + hash + ".css"); | ||
| } | ||
|
|
||
| /** | ||
| * Writes the compiled CSS atomically to its local {@code dotGenerated} path and uploads it. | ||
| * If the upload fails, the local file is kept: later requests on this node serve it, and | ||
| * eviction never removes it because it has no durable copy. | ||
| * | ||
| * @param target the local path of the compiled CSS | ||
| * @throws IOException if the local file cannot be written | ||
| * @throws DotDataException if the upload fails | ||
| */ | ||
| private void storeCompiledOutput(final File target) throws IOException, DotDataException { | ||
| final java.nio.file.Path path = target.toPath(); | ||
| java.nio.file.Files.createDirectories(path.getParent()); | ||
| final java.nio.file.Path temporary = java.nio.file.Files.createTempFile(path.getParent(), "css-", ".tmp"); | ||
| try { | ||
| java.nio.file.Files.write(temporary, this.output); | ||
| try { | ||
| java.nio.file.Files.move(temporary, path, java.nio.file.StandardCopyOption.ATOMIC_MOVE, | ||
| java.nio.file.StandardCopyOption.REPLACE_EXISTING); | ||
| } catch (java.nio.file.AtomicMoveNotSupportedException e) { | ||
| java.nio.file.Files.move(temporary, path, java.nio.file.StandardCopyOption.REPLACE_EXISTING); | ||
| } | ||
| APILocator.getBinaryAssetStorageAPI().storeGeneratedFile(target); | ||
| } finally { | ||
| java.nio.file.Files.deleteIfExists(temporary); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Handles special situations related to how Dart SASS notifies Users when errors or warnings were generated during | ||
| * the compilation. The {@link RuntimeUtils.TerminalOutput} contains important information generated by the CLI | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚪ [P3] DotLibSassCompiler.java:106 null live inode breaks live compile when live version is absent
Current code:
Problem: With the flag on and
live == true, a nullgetLiveInode()makesfind(null, …)throw, failing a request that compiled from the working inode before.Fix:
Assumption:
CSSPreProcessServletconfirms a live asset for the identifier before compiling, sogetLiveInode()should be non-null in practice. What to verify: that a published-then-unpublished SCSS file cannot reach this line in live mode; the null-guard removes the risk regardless.