WW-5080 Defines a new result type plain to use directly with Java code
diff --git a/core/src/main/java/org/apache/struts2/result/PlainResult.java b/core/src/main/java/org/apache/struts2/result/PlainResult.java
new file mode 100644
index 0000000..3cbeb09
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/PlainResult.java
@@ -0,0 +1,64 @@
+/*
+ * 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.result;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.Result;
+import org.apache.struts2.StrutsException;
+import org.apache.struts2.result.plain.HttpHeader;
+import org.apache.struts2.result.plain.ResponseBuilder;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+public interface PlainResult extends Result {
+
+ @Override
+ default void execute(ActionInvocation invocation) throws Exception {
+ ResponseBuilder builder = new ResponseBuilder();
+ write(builder);
+
+ HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
+
+ if (response.isCommitted()) {
+ throw new StrutsException("Http response already committed, cannot modify it!");
+ }
+
+ for (HttpHeader<String> header : builder.getStringHeaders()) {
+ response.addHeader(header.getName(), header.getValue());
+ }
+ for (HttpHeader<Long> header : builder.getDateHeaders()) {
+ response.addDateHeader(header.getName(), header.getValue());
+ }
+ for (HttpHeader<Integer> header : builder.getIntHeaders()) {
+ response.addIntHeader(header.getName(), header.getValue());
+ }
+
+ for (Cookie cookie : builder.getCookies()) {
+ response.addCookie(cookie);
+ }
+
+ response.getWriter().write(builder.getBody());
+ response.flushBuffer();
+ }
+
+ void write(ResponseBuilder response);
+
+}
+
diff --git a/core/src/main/java/org/apache/struts2/result/plain/BodyWriter.java b/core/src/main/java/org/apache/struts2/result/plain/BodyWriter.java
new file mode 100644
index 0000000..ff22769
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/BodyWriter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.result.plain;
+
+import java.io.StringWriter;
+
+class BodyWriter {
+
+ private final StringWriter body = new StringWriter();
+
+ public BodyWriter write(String out) {
+ body.write(out);
+ return this;
+ }
+
+ public BodyWriter writeLine(String out) {
+ body.write(out);
+ body.write("\n");
+ return this;
+ }
+
+ public String getBody() {
+ return body.toString();
+ }
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/DateHttpHeader.java b/core/src/main/java/org/apache/struts2/result/plain/DateHttpHeader.java
new file mode 100644
index 0000000..906860b
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/DateHttpHeader.java
@@ -0,0 +1,38 @@
+/*
+ * 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.result.plain;
+
+class DateHttpHeader implements HttpHeader<Long> {
+
+ private final String name;
+ private final Long value;
+
+ public DateHttpHeader(String name, Long value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Long getValue() {
+ return value;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/HttpCookies.java b/core/src/main/java/org/apache/struts2/result/plain/HttpCookies.java
new file mode 100644
index 0000000..22c105b
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/HttpCookies.java
@@ -0,0 +1,39 @@
+/*
+ * 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.result.plain;
+
+import javax.servlet.http.Cookie;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class HttpCookies {
+
+ private final List<Cookie> cookies = new ArrayList<>();
+
+ public HttpCookies add(String name, String value) {
+ cookies.add(new Cookie(name, value));
+ return this;
+ }
+
+ public List<Cookie> getCookies() {
+ return Collections.unmodifiableList(cookies);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/HttpHeader.java b/core/src/main/java/org/apache/struts2/result/plain/HttpHeader.java
new file mode 100644
index 0000000..90de858
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/HttpHeader.java
@@ -0,0 +1,27 @@
+/*
+ * 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.result.plain;
+
+public interface HttpHeader<T> {
+
+ String getName();
+
+ T getValue();
+
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/HttpHeaders.java b/core/src/main/java/org/apache/struts2/result/plain/HttpHeaders.java
new file mode 100644
index 0000000..31715c6
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/HttpHeaders.java
@@ -0,0 +1,58 @@
+/*
+ * 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.result.plain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class HttpHeaders {
+
+ private final List<HttpHeader<String>> stringHeaders = new ArrayList<>();
+ private final List<HttpHeader<Long>> dateHeaders = new ArrayList<>();
+ private final List<HttpHeader<Integer>> intHeaders = new ArrayList<>();
+
+ public HttpHeaders add(String name, String value) {
+ stringHeaders.add(new StringHttpHeader(name, value));
+ return this;
+ }
+
+ public HttpHeaders add(String name, Long value) {
+ dateHeaders.add(new DateHttpHeader(name, value));
+ return this;
+ }
+
+ public HttpHeaders add(String name, Integer value) {
+ intHeaders.add(new IntHttpHeader(name, value));
+ return this;
+ }
+
+ public List<HttpHeader<String>> getStringHeaders() {
+ return Collections.unmodifiableList(stringHeaders);
+ }
+
+ public List<HttpHeader<Long>> getDateHeaders() {
+ return Collections.unmodifiableList(dateHeaders);
+ }
+
+ public List<HttpHeader<Integer>> getIntHeaders() {
+ return Collections.unmodifiableList(intHeaders);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/IntHttpHeader.java b/core/src/main/java/org/apache/struts2/result/plain/IntHttpHeader.java
new file mode 100644
index 0000000..c190617
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/IntHttpHeader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.result.plain;
+
+class IntHttpHeader implements HttpHeader<Integer> {
+
+ private final String name;
+ private final Integer value;
+
+ public IntHttpHeader(String name, Integer value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Integer getValue() {
+ return value;
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/ResponseBuilder.java b/core/src/main/java/org/apache/struts2/result/plain/ResponseBuilder.java
new file mode 100644
index 0000000..5e1ae20
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/ResponseBuilder.java
@@ -0,0 +1,111 @@
+/*
+ * 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.result.plain;
+
+import javax.servlet.http.Cookie;
+
+public class ResponseBuilder {
+
+ public static final String CONTENT_TYPE = "Content-Type";
+
+ public static final String TEXT_PLAIN = "text/plain";
+ public static final String TEXT_HTML = "text/html";
+ public static final String APPLICATION_JSON = "application/json";
+
+ private final BodyWriter body;
+ private final HttpHeaders headers;
+ private final HttpCookies cookies;
+
+ public ResponseBuilder() {
+ this.body = new BodyWriter();
+ this.headers = new HttpHeaders().add(CONTENT_TYPE, TEXT_PLAIN + "; charset=UTF-8");
+ this.cookies = new HttpCookies();
+ }
+
+ public ResponseBuilder write(String out) {
+ body.write(out);
+ return this;
+ }
+
+ public ResponseBuilder writeLine(String out) {
+ body.writeLine(out);
+ return this;
+ }
+
+ public ResponseBuilder withHeader(String name, String value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public ResponseBuilder withHeader(String name, Long value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public ResponseBuilder withHeader(String name, Integer value) {
+ headers.add(name, value);
+ return this;
+ }
+
+ public ResponseBuilder withContentTypeTextPlain() {
+ headers.add(CONTENT_TYPE, TEXT_PLAIN + "; charset=UTF-8");
+ return this;
+ }
+
+ public ResponseBuilder withContentTypeTextHtml() {
+ headers.add(CONTENT_TYPE, TEXT_HTML + "; charset=UTF-8");
+ return this;
+ }
+
+ public ResponseBuilder withContentTypeJson() {
+ headers.add(CONTENT_TYPE, APPLICATION_JSON);
+ return this;
+ }
+
+ public ResponseBuilder withContentType(String contentType) {
+ headers.add(CONTENT_TYPE, contentType);
+ return this;
+ }
+
+ public ResponseBuilder withCookie(String name, String value) {
+ cookies.add(name, value);
+ return this;
+ }
+
+ public Iterable<HttpHeader<String>> getStringHeaders() {
+ return headers.getStringHeaders();
+ }
+
+ public Iterable<HttpHeader<Long>> getDateHeaders() {
+ return headers.getDateHeaders();
+ }
+
+ public Iterable<HttpHeader<Integer>> getIntHeaders() {
+ return headers.getIntHeaders();
+ }
+
+ public Iterable<Cookie> getCookies() {
+ return cookies.getCookies();
+ }
+
+ public String getBody() {
+ return body.getBody();
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/result/plain/StringHttpHeader.java b/core/src/main/java/org/apache/struts2/result/plain/StringHttpHeader.java
new file mode 100644
index 0000000..e4e3e03
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/result/plain/StringHttpHeader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.result.plain;
+
+class StringHttpHeader implements HttpHeader<String> {
+
+ private final String name;
+ private final String value;
+
+ public StringHttpHeader(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+}
diff --git a/core/src/test/java/org/apache/struts2/result/PlainResultTest.java b/core/src/test/java/org/apache/struts2/result/PlainResultTest.java
new file mode 100644
index 0000000..9e53cee
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/result/PlainResultTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.result;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+import org.apache.struts2.StrutsInternalTestCase;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+public class PlainResultTest extends StrutsInternalTestCase {
+
+ private MockHttpServletResponse response;
+ private MockActionInvocation invocation;
+
+ public void testWritePlainText() throws Exception {
+ PlainResult result = (PlainResult) response ->
+ response.write("test").withContentTypeTextPlain();
+
+ result.execute(invocation);
+
+ assertEquals("test", response.getContentAsString());
+ assertEquals("text/plain; charset=UTF-8", response.getContentType());
+ }
+
+ public void testWritePlainHtml() throws Exception {
+ PlainResult result = (PlainResult) response ->
+ response.write("<b>test</b>").withContentTypeTextHtml();
+
+ result.execute(invocation);
+
+ assertEquals("<b>test</b>", response.getContentAsString());
+ assertEquals("text/html; charset=UTF-8", response.getContentType());
+ }
+
+ public void testWriteJson() throws Exception {
+ PlainResult result = (PlainResult) response ->
+ response.write("{ 'value': 'test' }").withContentTypeJson();
+
+ result.execute(invocation);
+
+ assertEquals("{ 'value': 'test' }", response.getContentAsString());
+ assertEquals("application/json", response.getContentType());
+ }
+
+ public void testWriteContentTypeCsvWithCookie() throws Exception {
+ PlainResult result = (PlainResult) response ->
+ response.writeLine("name;value")
+ .withContentType("text/csv")
+ .withCookie("X-Test", "test")
+ .writeLine("line;1")
+ .write("line;2");
+
+ result.execute(invocation);
+
+ assertEquals("name;value\nline;1\nline;2", response.getContentAsString());
+ assertEquals("text/csv", response.getContentType());
+ }
+
+ public void testHeaders() throws Exception {
+ PlainResult result = (PlainResult) response ->
+ response.withHeader("X-String", "test")
+ .withHeader("X-Date", 0L)
+ .withHeader("X-Number", 100)
+ .write("");
+
+ result.execute(invocation);
+
+ assertEquals("", response.getContentAsString());
+ assertEquals("text/plain; charset=UTF-8", response.getContentType());
+ assertEquals("test", response.getHeader("X-String"));
+ assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeader("X-Date"));
+ assertEquals("100", response.getHeader("X-NUmber"));
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+ invocation = new MockActionInvocation();
+ response = new MockHttpServletResponse();
+ invocation.setInvocationContext(ActionContext.getContext());
+
+ ActionContext.getContext().withServletResponse(response).withActionInvocation(invocation);
+ }
+}
\ No newline at end of file