SLING-6183 - add Sling Model Exporter feature

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1767030 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 39bb93f..89da726 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,7 +115,8 @@
                             org.apache.sling.models.it.noclasses,
                             org.apache.sling.models.it.models,
                             org.apache.sling.models.it.rtbound,
-                            org.apache.sling.models.it.rtboundpicker
+                            org.apache.sling.models.it.rtboundpicker,
+                            org.apache.sling.models.it.exporter
                         </Sling-Model-Packages>
                         <Sling-Test-Regexp>.*Test</Sling-Test-Regexp>
                         <Export-Package>org.apache.sling.models.it</Export-Package>
@@ -242,7 +243,11 @@
                         <sling.additional.bundle.2>geronimo-atinject_1.0_spec</sling.additional.bundle.2>
                         <sling.additional.bundle.10>org.apache.sling.models.api</sling.additional.bundle.10>
                         <sling.additional.bundle.11>org.apache.sling.models.impl</sling.additional.bundle.11>
-                        <sling.additional.bundle.12>${project.build.finalName}.jar</sling.additional.bundle.12>
+                        <sling.additional.bundle.12>org.apache.sling.models.jacksonexporter</sling.additional.bundle.12>
+                        <sling.additional.bundle.13>jackson-annotations</sling.additional.bundle.13>
+                        <sling.additional.bundle.14>jackson-core</sling.additional.bundle.14>
+                        <sling.additional.bundle.15>jackson-databind</sling.additional.bundle.15>
+                        <sling.additional.bundle.16>${project.build.finalName}.jar</sling.additional.bundle.16>
                     </systemPropertyVariables>
                 </configuration>
             </plugin>
@@ -281,6 +286,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.engine</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.models.api</artifactId>
             <version>1.3.0-SNAPSHOT</version>
             <scope>provided</scope>
@@ -291,6 +302,31 @@
             <version>1.3.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.models.jacksonexporter</artifactId>
+            <version>0.0.99-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.3.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>2.3.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>2.3.2</version>
+            <scope>provided</scope>
+        </dependency>
+
         <!-- not part of launchpad 7 (see SLING-4710) -->
         <dependency>
             <groupId>org.apache.geronimo.specs</groupId>
diff --git a/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java b/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java
new file mode 100644
index 0000000..0f7d7d8
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sling.models.it.exporter;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Exporter;
+import org.apache.sling.models.annotations.Model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@Model(adaptables = { Resource.class }, resourceType = "sling/exp/base")
+@Exporter(name = "jackson", extensions = "json")
+public class BaseComponent {
+
+    private final Resource resource;
+
+    @Inject
+    private String sampleValue;
+
+    public BaseComponent(Resource resource) {
+        this.resource = resource;
+    }
+
+    public String getId() {
+        return this.resource.getPath();
+    }
+
+    public String getSampleValue() {
+        return sampleValue;
+    }
+
+    @JsonProperty(value="UPPER")
+    public String getSampleValueToUpperCase() {
+        return sampleValue.toUpperCase();
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/it/exporter/ExporterTest.java b/src/main/java/org/apache/sling/models/it/exporter/ExporterTest.java
new file mode 100644
index 0000000..5d6778c
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/exporter/ExporterTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.sling.models.it.exporter;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.engine.SlingRequestProcessor;
+import org.apache.sling.junit.annotations.SlingAnnotationsTestRunner;
+import org.apache.sling.junit.annotations.TestReference;
+import org.apache.sling.models.factory.MissingExporterException;
+import org.apache.sling.models.factory.ModelFactory;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(SlingAnnotationsTestRunner.class)
+public class ExporterTest {
+
+    @TestReference
+    private ResourceResolverFactory rrFactory;
+
+    @TestReference
+    private ModelFactory modelFactory;
+
+    @TestReference
+    private SlingRequestProcessor slingRequestProcessor;
+
+    private final String baseComponentPath = "/content/exp/baseComponent";
+    private final String childComponentPath = "/content/exp/childComponent";
+    private final String extendedComponentPath = "/content/exp/extendedComponent";
+    private Calendar testDate;
+
+    @Before
+    public void setup() throws LoginException, PersistenceException {
+        ResourceResolver adminResolver = null;
+        try {
+            adminResolver = rrFactory.getAdministrativeResourceResolver(null);
+            Map<String, Object> properties = new HashMap<String, Object>();
+            properties.put("sampleValue", "baseTESTValue");
+            properties.put(SlingConstants.NAMESPACE_PREFIX + ":" + SlingConstants.PROPERTY_RESOURCE_TYPE,
+                    "sling/exp/base");
+            ResourceUtil.getOrCreateResource(adminResolver, baseComponentPath, properties, null, false);
+            properties.clear();
+
+            properties.put("sampleValue", "childTESTValue");
+            properties.put(SlingConstants.NAMESPACE_PREFIX + ":" + SlingConstants.PROPERTY_RESOURCE_TYPE,
+                    "sling/exp/child");
+            properties.put(SlingConstants.NAMESPACE_PREFIX + ":" + SlingConstants.PROPERTY_RESOURCE_SUPER_TYPE,
+                    "sling/exp/base");
+            ResourceUtil.getOrCreateResource(adminResolver, childComponentPath, properties, null, false);
+            properties.clear();
+
+            properties.put("sampleValue", "extendedTESTValue");
+            properties.put(SlingConstants.NAMESPACE_PREFIX + ":" + SlingConstants.PROPERTY_RESOURCE_TYPE,
+                    "sling/exp/extended");
+            testDate = Calendar.getInstance();
+            testDate.setTimeZone(TimeZone.getTimeZone("UTC"));
+            testDate.setTimeInMillis(0);
+            testDate.set(2015, 6, 29);
+            properties.put("date", testDate);
+            ResourceUtil.getOrCreateResource(adminResolver, extendedComponentPath, properties, null, false);
+            adminResolver.commit();
+        } finally {
+            if (adminResolver != null && adminResolver.isLive()) {
+                adminResolver.close();
+            }
+        }
+    }
+
+    @Test
+    public void testExportToJSON() throws Exception {
+        ResourceResolver resolver = null;
+        try {
+            resolver = rrFactory.getAdministrativeResourceResolver(null);
+            final Resource baseComponentResource = resolver.getResource(baseComponentPath);
+            Assert.assertNotNull(baseComponentResource);
+            String jsonData = modelFactory.exportModelForResource(baseComponentResource, "jackson", String.class,
+                    Collections.<String, String> emptyMap());
+            Assert.assertTrue("JSON Data should contain the property value",
+                    StringUtils.contains(jsonData, "baseTESTValue"));
+
+            final Resource extendedComponentResource = resolver.getResource(extendedComponentPath);
+            Assert.assertNotNull(extendedComponentResource);
+            jsonData = modelFactory.exportModelForResource(extendedComponentResource, "jackson", String.class,
+                    Collections.<String, String> emptyMap());
+            Assert.assertTrue("JSON Data should contain the property value",
+                    StringUtils.contains(jsonData, "extendedTESTValue"));
+        } finally {
+            if (resolver != null && resolver.isLive()) {
+                resolver.close();
+            }
+        }
+    }
+
+    @Test
+    public void testExportToMap() throws Exception {
+        ResourceResolver resolver = null;
+        try {
+            resolver = rrFactory.getAdministrativeResourceResolver(null);
+            final Resource baseComponentResource = resolver.getResource(baseComponentPath);
+            Assert.assertNotNull(baseComponentResource);
+            Map<String, Object> data = modelFactory.exportModelForResource(baseComponentResource, "jackson", Map.class,
+                    Collections.<String, String> emptyMap());
+            Assert.assertEquals("Should have resource value", "baseTESTValue", data.get("sampleValue"));
+            Assert.assertEquals("Should have resource value", "BASETESTVALUE", data.get("UPPER"));
+        } finally {
+            if (resolver != null && resolver.isLive()) {
+                resolver.close();
+            }
+        }
+    }
+
+    @Test
+    public void testServlets() throws Exception {
+        ResourceResolver resolver = null;
+        try {
+            resolver = rrFactory.getAdministrativeResourceResolver(null);
+            FakeResponse response = new FakeResponse();
+            slingRequestProcessor.processRequest(new FakeRequest(baseComponentPath + ".model.json"), response, resolver);
+            JSONObject obj = new JSONObject(response.getStringWriter().toString());
+            Assert.assertEquals("application/json", response.getContentType());
+            Assert.assertEquals("BASETESTVALUE", obj.getString("UPPER"));
+            Assert.assertEquals(baseComponentPath, obj.getString("id"));
+
+            response = new FakeResponse();
+            slingRequestProcessor.processRequest(new FakeRequest(extendedComponentPath + ".model.json"), response, resolver);
+            obj = new JSONObject(response.getStringWriter().toString());
+            Assert.assertEquals(extendedComponentPath, obj.getString("id"));
+            Assert.assertEquals(testDate.getTimeInMillis(), obj.getLong("date"));
+        } finally {
+            if (resolver != null && resolver.isLive()) {
+                resolver.close();
+            }
+        }
+    }
+
+    @Test
+    public void testFailedExport() throws Exception {
+        boolean thrown = false;
+        try {
+            ResourceResolver resolver = null;
+            try {
+                resolver = rrFactory.getAdministrativeResourceResolver(null);
+                final Resource baseComponentResource = resolver.getResource(baseComponentPath);
+                Assert.assertNotNull(baseComponentResource);
+                String data = modelFactory.exportModelForResource(baseComponentResource, "jaxb", String.class,
+                        Collections.<String, String>emptyMap());
+                Assert.fail("Should have thrown missing serializer error.");
+            } finally {
+                if (resolver != null && resolver.isLive()) {
+                    resolver.close();
+                }
+            }
+        } catch (MissingExporterException e) {
+            thrown = true;
+            Assert.assertEquals("No exporter named jaxb supports java.lang.String.", e.getMessage());
+        }
+        Assert.assertTrue(thrown);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/models/it/exporter/ExtendedComponent.java b/src/main/java/org/apache/sling/models/it/exporter/ExtendedComponent.java
new file mode 100644
index 0000000..033a6f6
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/exporter/ExtendedComponent.java
@@ -0,0 +1,49 @@
+/*
+ * 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.sling.models.it.exporter;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Exporter;
+import org.apache.sling.models.annotations.Model;
+
+import javax.inject.Inject;
+
+@Model(adaptables = { Resource.class }, resourceType = "sling/exp/extended")
+@Exporter(name = "jackson", extensions = "json")
+public class ExtendedComponent extends BaseComponent {
+
+    @Inject
+    private Date date;
+
+    public ExtendedComponent(Resource resource) {
+        super(resource);
+    }
+
+    public Calendar getDateByCalendar() {
+        Calendar cal = new GregorianCalendar();
+        cal.setTime(date);
+        return cal;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/it/exporter/FakeRequest.java b/src/main/java/org/apache/sling/models/it/exporter/FakeRequest.java
new file mode 100644
index 0000000..a11da83
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/exporter/FakeRequest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.sling.models.it.exporter;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+class FakeRequest implements HttpServletRequest {
+
+    private final String path;
+
+    FakeRequest(String path) {
+        this.path = path;
+    }
+
+    @Override
+    public String getAuthType() {
+        return null;
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        return new Cookie[0];
+    }
+
+    @Override
+    public long getDateHeader(String name) {
+        return 0;
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return null;
+    }
+
+    @Override
+    public Enumeration getHeaders(String name) {
+        return null;
+    }
+
+    @Override
+    public Enumeration getHeaderNames() {
+        return null;
+    }
+
+    @Override
+    public int getIntHeader(String name) {
+        return 0;
+    }
+
+    @Override
+    public String getMethod() {
+        return "GET";
+    }
+
+    @Override
+    public String getPathInfo() {
+        return path;
+    }
+
+    @Override
+    public String getPathTranslated() {
+        return null;
+    }
+
+    @Override
+    public String getContextPath() {
+        return null;
+    }
+
+    @Override
+    public String getQueryString() {
+        return null;
+    }
+
+    @Override
+    public String getRemoteUser() {
+        return null;
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        return false;
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        return null;
+    }
+
+    @Override
+    public String getRequestedSessionId() {
+        return null;
+    }
+
+    @Override
+    public String getRequestURI() {
+        return null;
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        return null;
+    }
+
+    @Override
+    public String getServletPath() {
+        return "";
+    }
+
+    @Override
+    public HttpSession getSession(boolean create) {
+        return null;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return null;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        return false;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        return false;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        return false;
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromUrl() {
+        return false;
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        return null;
+    }
+
+    @Override
+    public Enumeration getAttributeNames() {
+        return null;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return null;
+    }
+
+    @Override
+    public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
+
+    }
+
+    @Override
+    public int getContentLength() {
+        return 0;
+    }
+
+    @Override
+    public String getContentType() {
+        return null;
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        return null;
+    }
+
+    @Override
+    public String getParameter(String name) {
+        return null;
+    }
+
+    @Override
+    public Enumeration getParameterNames() {
+        return null;
+    }
+
+    @Override
+    public String[] getParameterValues(String name) {
+        return new String[0];
+    }
+
+    @Override
+    public Map getParameterMap() {
+        return null;
+    }
+
+    @Override
+    public String getProtocol() {
+        return null;
+    }
+
+    @Override
+    public String getScheme() {
+        return null;
+    }
+
+    @Override
+    public String getServerName() {
+        return null;
+    }
+
+    @Override
+    public int getServerPort() {
+        return 0;
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException {
+        return null;
+    }
+
+    @Override
+    public String getRemoteAddr() {
+        return null;
+    }
+
+    @Override
+    public String getRemoteHost() {
+        return null;
+    }
+
+    @Override
+    public void setAttribute(String name, Object o) {
+
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+
+    }
+
+    @Override
+    public Locale getLocale() {
+        return null;
+    }
+
+    @Override
+    public Enumeration getLocales() {
+        return null;
+    }
+
+    @Override
+    public boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+        return null;
+    }
+
+    @Override
+    public String getRealPath(String path) {
+        return null;
+    }
+
+    @Override
+    public int getRemotePort() {
+        return 0;
+    }
+
+    @Override
+    public String getLocalName() {
+        return null;
+    }
+
+    @Override
+    public String getLocalAddr() {
+        return null;
+    }
+
+    @Override
+    public int getLocalPort() {
+        return 0;
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/it/exporter/FakeResponse.java b/src/main/java/org/apache/sling/models/it/exporter/FakeResponse.java
new file mode 100644
index 0000000..8747d3f
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/exporter/FakeResponse.java
@@ -0,0 +1,194 @@
+/*
+ * 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.sling.models.it.exporter;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Locale;
+
+class FakeResponse implements HttpServletResponse {
+
+    private StringWriter stringWriter = new StringWriter();
+    private String contentType = null;
+
+    @Override
+    public void addCookie(Cookie cookie) {
+    }
+
+    @Override
+    public boolean containsHeader(String name) {
+        return false;
+    }
+
+    @Override
+    public String encodeURL(String url) {
+        return null;
+    }
+
+    @Override
+    public String encodeRedirectURL(String url) {
+        return null;
+    }
+
+    @Override
+    public String encodeUrl(String url) {
+        return null;
+    }
+
+    @Override
+    public String encodeRedirectUrl(String url) {
+        return null;
+    }
+
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException {
+
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException {
+
+    }
+
+    @Override
+    public void setDateHeader(String name, long date) {
+
+    }
+
+    @Override
+    public void addDateHeader(String name, long date) {
+
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+
+    }
+
+    @Override
+    public void setIntHeader(String name, int value) {
+
+    }
+
+    @Override
+    public void addIntHeader(String name, int value) {
+
+    }
+
+    @Override
+    public void setStatus(int sc) {
+
+    }
+
+    @Override
+    public void setStatus(int sc, String sm) {
+
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return null;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return null;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException {
+        return new PrintWriter(stringWriter);
+    }
+
+    @Override
+    public void setCharacterEncoding(String charset) {
+
+    }
+
+    @Override
+    public void setContentLength(int len) {
+
+    }
+
+    @Override
+    public void setContentType(String type) {
+        this.contentType = type;
+    }
+
+    @Override
+    public void setBufferSize(int size) {
+
+    }
+
+    @Override
+    public int getBufferSize() {
+        return 0;
+    }
+
+    @Override
+    public void flushBuffer() throws IOException {
+
+    }
+
+    @Override
+    public void resetBuffer() {
+
+    }
+
+    @Override
+    public boolean isCommitted() {
+        return false;
+    }
+
+    @Override
+    public void reset() {
+
+    }
+
+    @Override
+    public void setLocale(Locale loc) {
+
+    }
+
+    @Override
+    public Locale getLocale() {
+        return null;
+    }
+
+    public StringWriter getStringWriter() {
+        return stringWriter;
+    }
+}