codeconsole commented on code in PR #15585:
URL: https://github.com/apache/grails-core/pull/15585#discussion_r3139945446


##########
grails-gsp/grails-sitemesh3/src/main/java/org/grails/plugins/sitemesh3/Sitemesh3CapturedPage.java:
##########
@@ -0,0 +1,359 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.grails.plugins.sitemesh3;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.CharBuffer;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.sitemesh.content.Content;
+import org.sitemesh.content.ContentChunk;
+import org.sitemesh.content.ContentProperty;
+import org.sitemesh.content.memory.InMemoryContent;
+import org.sitemesh.tagprocessor.CharSequenceBuffer;
+
+import org.grails.buffer.StreamCharBuffer;
+
+/**
+ * A SiteMesh 3 {@link Content} implementation that is populated by the GSP
+ * capture taglib at render time. Because the capture taglib runs during GSP
+ * execution, there is no need for SiteMesh to parse the response body; the
+ * data is already chunked up.
+ *
+ * <p>Backed by an {@link InMemoryContent} so that SiteMesh content properties
+ * can be traversed in the usual way (e.g. {@code head}, {@code body}, {@code
+ * title}, {@code page.<name>}, {@code meta.<name>}).</p>
+ */
+public class Sitemesh3CapturedPage implements Content {
+
+    public static final String REQUEST_ATTRIBUTE = 
Sitemesh3CapturedPage.class.getName();
+
+    private final InMemoryContent delegate = new InMemoryContent();
+
+    private StreamCharBuffer headBuffer;
+    private StreamCharBuffer bodyBuffer;
+    private StreamCharBuffer titleBuffer;
+    private StreamCharBuffer pageBuffer;
+    private CharSequence renderedContent;
+
+    private final Map<String, StreamCharBuffer> contentBuffers = new 
LinkedHashMap<>();
+    private final Map<String, String> pageProperties = new HashMap<>();
+
+    private boolean used;

Review Comment:
   Done — both are now volatile for the same reason as propertiesMaterialized.



##########
grails-gsp/grails-sitemesh3/src/main/java/org/grails/plugins/sitemesh3/Sitemesh3CapturedPage.java:
##########
@@ -0,0 +1,359 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.grails.plugins.sitemesh3;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.CharBuffer;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.sitemesh.content.Content;
+import org.sitemesh.content.ContentChunk;
+import org.sitemesh.content.ContentProperty;
+import org.sitemesh.content.memory.InMemoryContent;
+import org.sitemesh.tagprocessor.CharSequenceBuffer;
+
+import org.grails.buffer.StreamCharBuffer;
+
+/**
+ * A SiteMesh 3 {@link Content} implementation that is populated by the GSP
+ * capture taglib at render time. Because the capture taglib runs during GSP
+ * execution, there is no need for SiteMesh to parse the response body; the
+ * data is already chunked up.
+ *
+ * <p>Backed by an {@link InMemoryContent} so that SiteMesh content properties
+ * can be traversed in the usual way (e.g. {@code head}, {@code body}, {@code
+ * title}, {@code page.<name>}, {@code meta.<name>}).</p>
+ */
+public class Sitemesh3CapturedPage implements Content {
+
+    public static final String REQUEST_ATTRIBUTE = 
Sitemesh3CapturedPage.class.getName();
+
+    private final InMemoryContent delegate = new InMemoryContent();
+
+    private StreamCharBuffer headBuffer;
+    private StreamCharBuffer bodyBuffer;
+    private StreamCharBuffer titleBuffer;
+    private StreamCharBuffer pageBuffer;
+    private CharSequence renderedContent;
+
+    private final Map<String, StreamCharBuffer> contentBuffers = new 
LinkedHashMap<>();
+    private final Map<String, String> pageProperties = new HashMap<>();
+
+    private boolean used;
+    private boolean titleCaptured;
+    // Volatile because a captured page can be passed to an async dispatch
+    // thread (Grails 7 supports @Async controller returns and
+    // Callable-returning actions). Without volatile, the JMM gives no
+    // happens-before guarantee on the flag across threads, and two threads
+    // could race to materialize the property tree.
+    private volatile boolean propertiesMaterialized;
+
+    public void setHeadBuffer(StreamCharBuffer buffer) {
+        this.headBuffer = buffer;
+        markUsed();
+    }
+
+    public void setBodyBuffer(StreamCharBuffer buffer) {
+        this.bodyBuffer = buffer;
+        markUsed();
+    }
+
+    public void setTitleBuffer(StreamCharBuffer buffer) {
+        this.titleBuffer = buffer;
+    }
+
+    public void setPageBuffer(StreamCharBuffer buffer) {
+        this.pageBuffer = buffer;
+    }
+
+    // Attaches fully-rendered content (e.g. a layout's output after
+    // inline-expanded taglibs have run) as the page's data, bypassing the
+    // HTML parse step that would otherwise build the data from captured
+    // buffers. Held as a CharSequence so callers can pass a CharBuffer
+    // straight through without allocating an intermediate String — the
+    // RawDataChunk writes via Writer.write(char[], int, int) when possible.
+    public void setRenderedContent(CharSequence content) {
+        this.renderedContent = content;
+        markUsed();
+    }
+
+    public StreamCharBuffer getHeadBuffer() {
+        return headBuffer;
+    }
+
+    public StreamCharBuffer getBodyBuffer() {
+        return bodyBuffer;
+    }
+
+    public StreamCharBuffer getTitleBuffer() {
+        return titleBuffer;
+    }
+
+    public StreamCharBuffer getPageBuffer() {
+        return pageBuffer;
+    }
+
+    public void addContentBuffer(String tag, StreamCharBuffer buffer) {
+        contentBuffers.put(tag, buffer);
+        markUsed();
+    }
+
+    public void addProperty(String name, String value) {
+        if (name == null || value == null) {
+            return;
+        }
+        pageProperties.put(name, value);
+        markUsed();
+    }
+
+    public boolean isUsed() {
+        return used;
+    }
+
+    public void markUsed() {
+        this.used = true;
+    }
+
+    public boolean isTitleCaptured() {
+        return titleCaptured;
+    }
+
+    public void setTitleCaptured(boolean titleCaptured) {
+        this.titleCaptured = titleCaptured;
+    }
+
+    /**
+     * Writes the full original page (unmerged) to the given appendable.
+     * Used when decoration is skipped and the caller needs to fall back to
+     * the raw response.
+     */
+    public void writeOriginal(Appendable out) throws IOException {
+        if (pageBuffer != null) {
+            pageBuffer.writeTo(appendableToWriter(out));
+        }
+    }
+
+    @Override
+    public ContentChunk getData() {
+        materializeProperties();
+        if (renderedContent != null) {
+            return new RawDataChunk(renderedContent, this);
+        }
+        return delegate.getData();
+    }
+
+    @Override
+    public ContentProperty getExtractedProperties() {
+        materializeProperties();
+        return delegate.getExtractedProperties();
+    }
+
+    @Override
+    public CharSequenceBuffer createDataOnlyBuffer() {
+        return delegate.createDataOnlyBuffer();
+    }
+
+    private void materializeProperties() {
+        if (propertiesMaterialized) {
+            return;
+        }
+        propertiesMaterialized = true;

Review Comment:
   Fixed — extracted body into doMaterializeProperties() and wrapped the entry 
in synchronized(this) with a double-check.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to