diff --git a/core/src/main/java/org/apache/struts2/components/Compress.java b/core/src/main/java/org/apache/struts2/components/Compress.java new file mode 100644 index 0000000000..6131e5f332 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/Compress.java @@ -0,0 +1,102 @@ +/* + * 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 + * + * http://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.apache.struts2.components; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.util.ValueStack; +import org.apache.struts2.views.annotations.StrutsTag; +import org.apache.struts2.views.annotations.StrutsTagAttribute; + +import java.io.Writer; + +/** + *

+ * Used to compress HTML output. Just wrap a given section with the tag. + *

+ * + *

+ * Configurable attributes are: + *

+ * + * + * + *

Examples

+ *
+ *  
+ *  <s:compress>
+ *    <s:form action="submit">
+ *    <s:text name="name" />
+ *    ...
+ *    </s:form>
+ *  </s:compress>
+ *  
+ * 
+ * + *

Uses conditional compression depending on action

+ *
+ *  
+ *  <s:compress force="shouldCompress">
+ *    <s:form action="submit">
+ *    <s:text name="name" />
+ *    ...
+ *    </s:form>
+ *  </s:compress>
+ *  
+ * 
+ * "shouldCompress" is a field with getter define on action used in expression evaluation + */ +@StrutsTag(name = "compress", tldTagClass = "org.apache.struts2.views.jsp.CompressTag", + description = "Compress wrapped content") +public class Compress extends Component { + + private static final Logger LOG = LogManager.getLogger(Compress.class); + + private String force; + + public Compress(ValueStack stack) { + super(stack); + } + + @Override + public boolean end(Writer writer, String body) { + Object forceValue = findValue(force, Boolean.class); + boolean forced = forceValue != null && Boolean.parseBoolean(forceValue.toString()); + if (devMode && !forced) { + LOG.debug("Avoids compressing output: {} in DevMode", body); + return super.end(writer, body, true); + } + LOG.trace("Compresses: {}", body); + String compressed = body.trim().replaceAll(">\\s+<", "><"); + LOG.trace("Compressed: {}", compressed); + return super.end(writer, compressed, true); + } + + @Override + public boolean usesBody() { + return true; + } + + @StrutsTagAttribute(description = "Force output compression") + public void setForce(String force) { + this.force = force; + } +} diff --git a/core/src/main/java/org/apache/struts2/views/jsp/CompressTag.java b/core/src/main/java/org/apache/struts2/views/jsp/CompressTag.java new file mode 100644 index 0000000000..4b9821e539 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/views/jsp/CompressTag.java @@ -0,0 +1,55 @@ +/* + * 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 + * + * http://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.apache.struts2.views.jsp; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.struts2.components.Component; +import org.apache.struts2.components.Compress; +import org.apache.struts2.util.ValueStack; + +import java.io.Serial; + +/** + * @see org.apache.struts2.components.Compress + */ +public class CompressTag extends ComponentTagSupport { + + @Serial + private static final long serialVersionUID = 7572566991679717145L; + + private String force; + + @Override + public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { + return new Compress(stack); + } + + @Override + protected void populateParams() { + super.populateParams(); + + Compress compress = (Compress) component; + compress.setForce(force); + } + + public void setForce(String force) { + this.force = force; + } +} diff --git a/core/src/main/resources/template/css_xhtml/controlfooter.ftl b/core/src/main/resources/template/css_xhtml/controlfooter.ftl index d25b5790cf..0099b6df88 100644 --- a/core/src/main/resources/template/css_xhtml/controlfooter.ftl +++ b/core/src/main/resources/template/css_xhtml/controlfooter.ftl @@ -19,29 +19,26 @@ */ --> ${attributes.after!}<#t/> - <#lt/> <#if !attributes.labelPosition?? && (attributes.form.labelPosition)??> <#assign labelPos = attributes.form.labelPosition/> <#elseif attributes.labelPosition??> <#assign labelPos = attributes.labelPosition/> <#if (labelPos!"top") == 'top'> - <#rt/> +<#rt/> <#else> - <#rt/> +<#rt/> <#if (attributes.errorposition!"top") == 'bottom'> <#assign hasFieldErrors = attributes.name?? && fieldErrors?? && fieldErrors.get(attributes.name)??/> <#if hasFieldErrors>
<#if attributes.id??>id="wwerr_${attributes.id}"<#rt/> class="wwerr"> <#list fieldErrors.get(attributes.name) as error> - - <#if attributes.id??> - errorFor="${attributes.id}"<#rt/> - - class="errorMessage"> - ${error} -
<#t/> + +<#if attributes.id??> + errorFor="${attributes.id}"<#rt/> + + class="errorMessage">${error}<#rt/> <#t/> diff --git a/core/src/main/resources/template/css_xhtml/form-validate.ftl b/core/src/main/resources/template/css_xhtml/form-validate.ftl index 7beb7fab5f..1f1bff261f 100644 --- a/core/src/main/resources/template/css_xhtml/form-validate.ftl +++ b/core/src/main/resources/template/css_xhtml/form-validate.ftl @@ -20,9 +20,9 @@ --> <#if attributes.validate!false == true> <@s.script src="${base}${attributes.staticContentPath}/css_xhtml/validation.js"/> - <#if attributes.onsubmit??> - ${tag.addParameter('onsubmit', "${attributes.onsubmit}; return validateForm_${attributes.escapedId}();")} - <#else> - ${tag.addParameter('onsubmit', "return validateForm_${attributes.escapedId}();")} - +<#if attributes.onsubmit??> + ${tag.addParameter('onsubmit', "${attributes.onsubmit}; return validateForm_${attributes.escapedId}();")} +<#else> + ${tag.addParameter('onsubmit', "return validateForm_${attributes.escapedId}();")} + diff --git a/core/src/main/resources/template/css_xhtml/label.ftl b/core/src/main/resources/template/css_xhtml/label.ftl index 23701e7159..4c03cd19a5 100644 --- a/core/src/main/resources/template/css_xhtml/label.ftl +++ b/core/src/main/resources/template/css_xhtml/label.ftl @@ -18,7 +18,6 @@ * under the License. */ --> -<#--include "/${attributes.templateDir}/css_xhtml/controlheader.ftl" /--> <#include "/${attributes.templateDir}/${attributes.expandTheme}/controlheader.ftl" /> <#if attributes.id??> diff --git a/core/src/main/resources/template/simple/actionerror.ftl b/core/src/main/resources/template/simple/actionerror.ftl index 4cd9a60b93..54b3f3ca20 100644 --- a/core/src/main/resources/template/simple/actionerror.ftl +++ b/core/src/main/resources/template/simple/actionerror.ftl @@ -19,7 +19,7 @@ */ --> <#if (actionErrors?? && actionErrors?size > 0)> - + <#if attributes.id??> id="${attributes.id}"<#rt/> @@ -32,10 +32,10 @@ style="${attributes.cssStyle}"<#rt/> > - <#list actionErrors as error> - <#if error??> -
  • <#if attributes.escape>${error!}<#else>${error!?no_esc}<#rt/>
  • <#rt/> - - - +<#list actionErrors as error> +<#if error??> +
  • <#if attributes.escape>${error!}<#else>${error!?no_esc}<#rt/>
  • <#rt/> + + + \ No newline at end of file diff --git a/core/src/main/resources/template/simple/form-close-tooltips.ftl b/core/src/main/resources/template/simple/form-close-tooltips.ftl index 1bf45e909e..e57bdcc1e3 100644 --- a/core/src/main/resources/template/simple/form-close-tooltips.ftl +++ b/core/src/main/resources/template/simple/form-close-tooltips.ftl @@ -18,7 +18,6 @@ * under the License. */ --> - <#-- Code that will add javascript needed for tooltips --><#t/> diff --git a/core/src/main/resources/template/simple/form-close.ftl b/core/src/main/resources/template/simple/form-close.ftl index 59d4f0dbc2..291af09778 100644 --- a/core/src/main/resources/template/simple/form-close.ftl +++ b/core/src/main/resources/template/simple/form-close.ftl @@ -19,7 +19,6 @@ */ --> - <#if (attributes.customOnsubmitEnabled??)> <@s.script> <#-- @@ -98,5 +97,4 @@ - <#include "/${attributes.templateDir}/${attributes.expandTheme}/form-close-tooltips.ftl" /> diff --git a/core/src/main/resources/template/xhtml/controlheader.ftl b/core/src/main/resources/template/xhtml/controlheader.ftl index ec9372d927..2df6b1cb0d 100644 --- a/core/src/main/resources/template/xhtml/controlheader.ftl +++ b/core/src/main/resources/template/xhtml/controlheader.ftl @@ -19,10 +19,10 @@ */ --> <#include "/${attributes.templateDir}/${attributes.expandTheme}/controlheader-core.ftl" /> - - class="align-${attributes.align}" - <#else > - class="tdInput" - + +<#if attributes.align?? > + class="align-${attributes.align}"<#rt/> +<#else > + class="tdInput"<#rt/> + ><#t/> diff --git a/core/src/main/resources/template/xhtml/form-validate.ftl b/core/src/main/resources/template/xhtml/form-validate.ftl index a074bef2fd..cbd03a7f68 100644 --- a/core/src/main/resources/template/xhtml/form-validate.ftl +++ b/core/src/main/resources/template/xhtml/form-validate.ftl @@ -19,10 +19,10 @@ */ --> <#if attributes.validate!false == true> - <@s.script src="${base}${attributes.staticContentPath}/xhtml/validation.js" /> - <#if attributes.onsubmit??> - ${tag.addParameter('onsubmit', "${attributes.onsubmit}; return validateForm_${attributes.escapedId}();")} - <#else> - ${tag.addParameter('onsubmit', "return validateForm_${attributes.escapedId}();")} - +<@s.script src="${base}${attributes.staticContentPath}/xhtml/validation.js" /> +<#if attributes.onsubmit??> + ${tag.addParameter('onsubmit', "${attributes.onsubmit}; return validateForm_${attributes.escapedId}();")} +<#else> + ${tag.addParameter('onsubmit', "return validateForm_${attributes.escapedId}();")} + diff --git a/core/src/site/resources/tags/compress-attributes.html b/core/src/site/resources/tags/compress-attributes.html new file mode 100644 index 0000000000..9ecf24227c --- /dev/null +++ b/core/src/site/resources/tags/compress-attributes.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Dynamic Attributes Allowed:

    false

    Name

    Required

    Default

    Evaluated

    Type

    Description

    forcefalsefalseStringForce output compression
    performClearTagStateForTagPoolingServersfalsefalsefalseBooleanWhether to clear all tag state during doEndTag() processing (if applicable)
    diff --git a/core/src/site/resources/tags/compress-description.html b/core/src/site/resources/tags/compress-description.html new file mode 100644 index 0000000000..6865f0b720 --- /dev/null +++ b/core/src/site/resources/tags/compress-description.html @@ -0,0 +1 @@ +Compress wrapped content diff --git a/core/src/test/java/org/apache/struts2/components/CompressTest.java b/core/src/test/java/org/apache/struts2/components/CompressTest.java new file mode 100644 index 0000000000..6631850ab7 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/components/CompressTest.java @@ -0,0 +1,132 @@ +/* + * 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 + * + * http://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.apache.struts2.components; + +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.util.ValueStack; +import org.apache.struts2.util.ValueStackFactory; + +import java.io.StringWriter; +import java.util.Map; + +public class CompressTest extends StrutsInternalTestCase { + + private ValueStack stack; + private Map context; + + public void testCompressHtmlOutput() { + Compress compress = new Compress(stack); + + String body = """ + + + File upload: result + + +

    File upload: result

    + + + """; + + StringWriter writer = new StringWriter(); + + compress.setDevMode("false"); + compress.setForce("false"); + compress.end(writer, body); + + assertEquals("File upload: result

    File upload: result

    ", writer.toString()); + } + + public void testAvoidCompressingInDevModeHtmlOutput() { + Compress compress = new Compress(stack); + + String body = """ + + + File upload: result + + +

    File upload: result

    + + + """; + + StringWriter writer = new StringWriter(); + + compress.setDevMode("true"); + compress.end(writer, body); + + assertEquals(body, writer.toString()); + } + + public void testCompressHtmlOutputEvenInDevMode() { + Compress compress = new Compress(stack); + + String body = """ + + + File upload: result + + +

    File upload: result

    + + + """; + + StringWriter writer = new StringWriter(); + + compress.setDevMode("true"); + compress.setForce("true"); + compress.end(writer, body); + + assertEquals("File upload: result

    File upload: result

    ", writer.toString()); + } + + public void testCompressHtmlOutputEvenInDevModeAndForceIsExpression() { + Compress compress = new Compress(stack); + + String body = """ + + + File upload: result + + +

    File upload: result

    + + + """; + + this.context.put("shouldCompress", Boolean.TRUE); + + compress.setDevMode("true"); + compress.setForce("shouldCompress"); + + StringWriter writer = new StringWriter(); + compress.end(writer, body); + + assertEquals("File upload: result

    File upload: result

    ", writer.toString()); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + stack = container.getInstance(ValueStackFactory.class).createValueStack(); + context = stack.getContext(); + } +} diff --git a/core/src/test/java/org/apache/struts2/views/jsp/AbstractUITagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/AbstractUITagTest.java index df1e4323d0..6252c4554f 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/AbstractUITagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/AbstractUITagTest.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ServletActionContext; +import org.apache.struts2.StrutsConstants; import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.DefaultActionMapper; import org.apache.struts2.views.jsp.ui.AbstractUITag; @@ -295,4 +296,18 @@ public static String normalize(Object obj, boolean appendSpace) { return buffer.toString(); } + + protected void setDevMode(final boolean devMode) { + setStrutsConstant(Collections.singletonMap(StrutsConstants.STRUTS_DEVMODE, Boolean.toString(devMode))); + } + + /** + * Overwrite the Struts Constant and reload container + */ + @Override + protected void setStrutsConstant(final Map overwritePropeties) { + super.setStrutsConstant(overwritePropeties); + stack.getActionContext().withContainer(container); + } + } diff --git a/core/src/test/java/org/apache/struts2/views/jsp/CompressTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/CompressTagTest.java new file mode 100644 index 0000000000..994e948af8 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/views/jsp/CompressTagTest.java @@ -0,0 +1,73 @@ +/* + * 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 + * + * http://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.apache.struts2.views.jsp; + +import org.apache.struts2.views.jsp.ui.StrutsBodyContent; + +public class CompressTagTest extends AbstractUITagTest { + + public void testNoCompression() throws Exception { + setDevMode(true); + + CompressTag tag = new CompressTag(); + tag.setPageContext(pageContext); + + StrutsBodyContent bc = new StrutsBodyContent(null); + bc.print(""" +
    +
    + """ + ); + + tag.doStartTag(); + tag.setBodyContent(bc); + tag.doEndTag(); + + assertEquals(""" +
    +
    + """.stripTrailing(), + this.writer.toString()); + } + + public void testForceCompression() throws Exception { + setDevMode(true); + + CompressTag tag = new CompressTag(); + tag.setPageContext(pageContext); + tag.setForce("true"); + + StrutsBodyContent bc = new StrutsBodyContent(null); + bc.print(""" +
    +
    + """ + ); + + tag.doStartTag(); + tag.setBodyContent(bc); + tag.doEndTag(); + + assertEquals(""" +
    + """.stripTrailing(), + this.writer.toString()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java index d1e89a099e..ffc706e0f9 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java @@ -19,13 +19,9 @@ package org.apache.struts2.views.jsp.ui; import org.apache.commons.lang3.StringUtils; -import org.apache.struts2.StrutsConstants; import org.apache.struts2.dispatcher.PrepareOperations; import org.apache.struts2.views.jsp.AbstractUITagTest; -import java.util.Collections; -import java.util.Map; - /** * Test case for {@link org.apache.struts2.components.Debug}. */ @@ -203,16 +199,4 @@ public void testTagAttributeOverrideDevModeFalse_clearTagStateSet() throws Excep PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used. } - private void setDevMode(final boolean devMode) { - setStrutsConstant(Collections.singletonMap(StrutsConstants.STRUTS_DEVMODE, Boolean.toString(devMode))); - } - - /** - * Overwrite the Struts Constant and reload container - */ - @Override - protected void setStrutsConstant(final Map overwritePropeties) { - super.setStrutsConstant(overwritePropeties); - stack.getActionContext().withContainer(container); - } }