This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch SLING-10871
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-api.git


The following commit(s) were added to refs/heads/SLING-10871 by this push:
     new 860ca8b  SLING-10871 : Add builder API for request/resource objects
860ca8b is described below

commit 860ca8bb40c2bf236c25ad74ae1c3ee13b9aaef9
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Sat Oct 16 10:01:53 2021 +0200

    SLING-10871 : Add builder API for request/resource objects
---
 .../apache/sling/api/request/builder/Builders.java |  45 ++
 .../builder/SlingHttpServletRequestBuilder.java    | 140 ++++
 .../builder/SlingHttpServletResponseBuilder.java   |  39 +
 .../builder/SlingHttpServletResponseResult.java    |  78 ++
 .../api/request/builder/impl/HeaderSupport.java    | 122 +++
 .../api/request/builder/impl/HttpSessionImpl.java  | 154 ++++
 .../request/builder/impl/RequestParameterImpl.java |  92 +++
 .../builder/impl/RequestParameterMapImpl.java      |  56 ++
 .../request/builder/impl/RequestPathInfoImpl.java  |  80 ++
 .../builder/impl/RequestProgressTrackerImpl.java   |  71 ++
 .../request/builder/impl/ServletContextImpl.java   | 306 +++++++
 .../builder/impl/SlingHttpServletRequestImpl.java  | 892 +++++++++++++++++++++
 .../builder/impl/SlingHttpServletResponseImpl.java | 387 +++++++++
 .../sling/api/request/builder/package-info.java    |  20 +
 .../sling/api/request/builder/BuildersTest.java    |  66 ++
 .../request/builder/impl/HeaderSupportTest.java    | 116 +++
 .../request/builder/impl/HttpSessionImplTest.java  | 104 +++
 .../builder/impl/RequestParameterImplTest.java     |  47 ++
 .../builder/impl/RequestParameterMapImplTest.java  |  49 ++
 .../builder/impl/RequestPathInfoImplTest.java      | 103 +++
 .../impl/RequestProgressTrackerImplTest.java       |  42 +
 .../builder/impl/ServletContextImplTest.java       | 263 ++++++
 .../impl/SlingHttpServletRequestImplTest.java      | 520 ++++++++++++
 .../impl/SlingHttpServletResponseImplTest.java     | 248 ++++++
 24 files changed, 4040 insertions(+)

diff --git a/src/main/java/org/apache/sling/api/request/builder/Builders.java 
b/src/main/java/org/apache/sling/api/request/builder/Builders.java
new file mode 100644
index 0000000..7e5e5d2
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/request/builder/Builders.java
@@ -0,0 +1,45 @@
+/*
+ * 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.api.request.builder;
+
+import org.apache.sling.api.request.builder.impl.SlingHttpServletRequestImpl;
+import org.apache.sling.api.request.builder.impl.SlingHttpServletResponseImpl;
+import org.apache.sling.api.resource.Resource;
+import org.jetbrains.annotations.NotNull;
+
+public class Builders {
+
+    /**
+     * Create a new request builder
+     * @param resource The resource 
+     * @return A builder
+     * @throws IllegalArgumentException If resource is {@code null}
+     */
+    public static @NotNull SlingHttpServletRequestBuilder 
newRequestBuilder(@NotNull final Resource resource) {
+        return new SlingHttpServletRequestImpl(resource);
+    }
+
+    /**
+     * Create a new response builder
+     * @return A builder
+     */
+    public static @NotNull SlingHttpServletResponseBuilder 
newResponseBuilder() {
+        return new SlingHttpServletResponseImpl();
+    }
+}
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletRequestBuilder.java
 
b/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletRequestBuilder.java
new file mode 100644
index 0000000..9f661a8
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletRequestBuilder.java
@@ -0,0 +1,140 @@
+/*
+ * 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.api.request.builder;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/** 
+ * Fluent helper for building a request.
+ * 
+ * Instances of this interface are not thread-safe.
+ */
+@ProviderType
+public interface SlingHttpServletRequestBuilder {
+
+    /** 
+     * Set the HTTP request method to use - defaults to GET
+     * @param method The HTTP method
+     * @return this object
+     * @throws IllegalArgumentException If method is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder withRequestMethod(@NotNull String 
method);
+
+    /** 
+     * Set the HTTP request's Content-Type
+     * @param content type
+     * @return this object
+     */
+    @NotNull SlingHttpServletRequestBuilder withContentType(String 
contentType);
+
+    /** 
+     * Use the supplied content as the request's body content
+     * @param content the content
+     * @return this object
+     */
+    @NotNull SlingHttpServletRequestBuilder withBody(String content);
+
+    /**
+     * Sets the optional selectors of the internal request, which influence
+     * the Servlet/Script resolution.
+     * @param selectors The selectors
+     * @return this object
+     */
+    @NotNull SlingHttpServletRequestBuilder withSelectors(String ... 
selectors);
+
+    /** 
+     * Sets the optional extension of the internal request, which influence
+     * the Servlet/Script resolution.
+     * @param extension The extension
+     * @return this object
+     */
+    @NotNull SlingHttpServletRequestBuilder withExtension(String extension);
+
+    /** 
+     * Set a request parameter
+     * @param key The name of the parameter
+     * @param value The value of the parameter
+     * @return this object
+     * @throws IllegalArgumentException If key or value is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder withParameter(@NotNull String key, 
@NotNull String value);
+
+    /** 
+     * Set a request parameter
+     * @param key The name of the parameter
+     * @param values The values of the parameter
+     * @return this object
+     * @throws IllegalArgumentException If key or values is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder withParameter(@NotNull String key, 
@NotNull String[] values);
+
+    /** 
+     * Add the supplied request parameters to the current ones.
+     * @param parameters Additional parameters
+     * @return this object
+     * @throws IllegalArgumentException If parameters is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder withParameters(@NotNull 
Map<String, String[]> parameters);
+
+    /** 
+     * Use the request dispatcher from the provided request.
+     * @param request The request
+     * @return this object
+     * @throws IllegalArgumentException If request is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder useRequestDispatcherFrom(@NotNull 
SlingHttpServletRequest request);
+
+    /** 
+     * If a session is used, use the session from the provided request
+     * @param request The request
+     * @return this object
+     * @throws IllegalArgumentException If request is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder useSessionFrom(@NotNull 
HttpServletRequest request);
+
+    /** 
+     * Use the attributes backed by the provided request
+     * @param request The request
+     * @return this object
+     * @throws IllegalArgumentException If request is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder useAttributesFrom(@NotNull 
HttpServletRequest request);
+
+    /** 
+     * Use the servlet context from the provided request
+     * @param request The request
+     * @return this object
+     * @throws IllegalArgumentException If request is {@code null}
+     */
+    @NotNull SlingHttpServletRequestBuilder useServletContextFrom(@NotNull 
HttpServletRequest request);
+
+    /**
+     * Build the request.
+     * Once this method has been called, the builder must not be used anymore. 
A new builder
+     * needs to be created, to create a new request.
+     * @return A request object
+     */
+    @NotNull SlingHttpServletRequest build();
+}
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletResponseBuilder.java
 
b/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletResponseBuilder.java
new file mode 100644
index 0000000..23f4752
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletResponseBuilder.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.sling.api.request.builder;
+
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/** 
+ * Fluent helper for building a response.
+ * 
+ * Instances of this interface are not thread-safe.
+ */
+@ProviderType
+public interface SlingHttpServletResponseBuilder {
+
+    /**
+     * Build the response.
+     * Once this method has been called, the builder must not be used anymore. 
A new builder
+     * needs to be created, to create a new response.
+     * @return A response object
+     */
+    @NotNull SlingHttpServletResponseResult build();
+}
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletResponseResult.java
 
b/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletResponseResult.java
new file mode 100644
index 0000000..823c7d9
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/SlingHttpServletResponseResult.java
@@ -0,0 +1,78 @@
+/*
+ * 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.api.request.builder;
+
+import javax.servlet.http.Cookie;
+
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/** 
+ * This is an extension of a {@link SlingHttpServletResponse} to get
+ * the result from a processing.
+ * 
+ * Instances of this interface are not thread-safe.
+ */
+@ProviderType
+public interface SlingHttpServletResponseResult extends 
SlingHttpServletResponse {
+
+    /**
+     * Build the response.
+     * Once this method has been called, the builder must not be used anymore. 
A new builder
+     * needs to be created, to create a new response.
+     * @return A response object
+     */
+    @NotNull SlingHttpServletResponse build();
+
+    /**
+     * Get the content length
+     * @return The content length or {@code -1} if not set
+     */
+    long getContentLength();
+
+    /**
+     * Get the status message
+     * @return The status message or {@code null}.
+     */
+    String getStatusMessage();
+
+    /**
+     * Get the named cookie
+     * @param name The name of the cookie
+     * @return The cookie or {@code null} if no cookie with that name exists.
+     */
+    public Cookie getCookie(String name);
+
+    /**
+     * Get all cookies
+     * @return The array of cookies or {@code null} if no cookies are set.
+     */
+    Cookie[] getCookies();
+
+    /**
+     * Get the output as a byte array
+     */
+    byte [] getOutput();
+
+    /**
+     * Get the output as a string
+     */
+    String getOutputAsString();
+}
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/HeaderSupport.java 
b/src/main/java/org/apache/sling/api/request/builder/impl/HeaderSupport.java
new file mode 100644
index 0000000..f0f8793
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/request/builder/impl/HeaderSupport.java
@@ -0,0 +1,122 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manage HTTP headers for request and response.
+ */
+public class HeaderSupport {
+
+    private static final DateTimeFormatter RFC_1123_DATE_TIME = 
DateTimeFormatter.RFC_1123_DATE_TIME;
+
+    private final Map<String, List<String>> headers = new LinkedHashMap<>();
+
+    public void addHeader(final String name, final String value) {
+        this.headers.computeIfAbsent(name, key -> new 
ArrayList<>()).add(value);
+    }
+
+    public void addIntHeader(final String name, final int value) {
+        this.addHeader(name, Integer.toString(value));
+    }
+
+    public void addDateHeader(final String name, final long date) {
+        final Date d = new Date(date);
+        this.addHeader(name, 
RFC_1123_DATE_TIME.format(d.toInstant().atOffset(ZoneOffset.UTC)));
+    }
+
+    public void setHeader(final String name, final String value) {
+        removeHeaders(name);
+        addHeader(name, value);
+    }
+
+    public void setIntHeader(final String name, final int value) {
+        removeHeaders(name);
+        addIntHeader(name, value);
+    }
+
+    public void setDateHeader(final String name, final long date) {
+        removeHeaders(name);
+        addDateHeader(name, date);
+    }
+
+    private void removeHeaders(final String name) {
+        this.headers.remove(name);
+    }
+
+    public boolean containsHeader(final String name) {
+        return this.headers.get(name) != null;
+    }
+
+    public String getHeader(final String name) {
+        final List<String> values = this.headers.get(name);
+        if ( values == null ) {
+            return null;
+        }
+        return values.get(0);
+    }
+
+    public int getIntHeader(String name) {
+        final String value = getHeader(name);
+        if ( value != null ) {
+            return Integer.valueOf(value);
+        }
+        return -1;
+    }
+
+    public long getDateHeader(final String name) {
+        final String value = getHeader(name);
+        if ( value != null ) {
+            try {                
+                final Date date = Date.from(ZonedDateTime.parse(value, 
RFC_1123_DATE_TIME).toInstant());
+                return date.getTime();
+            } catch (final DateTimeParseException ex) {
+                throw new IllegalArgumentException("Invalid date value: " + 
value, ex);                
+            }
+        }
+        return -1L;
+    }
+
+    public Collection<String> getHeaders(final String name) {
+        final List<String> values = new ArrayList<String>();
+        final List<String> headers = this.headers.get(name);
+        if ( headers != null ) {
+            values.addAll(headers);
+        }
+        return values;
+    }
+
+    public Collection<String> getHeaderNames() {
+        return this.headers.keySet();
+    }
+
+    public void reset() {
+        this.headers.clear();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/HttpSessionImpl.java 
b/src/main/java/org/apache/sling/api/request/builder/impl/HttpSessionImpl.java
new file mode 100644
index 0000000..19db6a2
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/HttpSessionImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+
+/**
+ * Internal {@link HttpSession} implementation.
+ */
+public class HttpSessionImpl implements HttpSession {
+
+    private final ServletContext servletContext;
+    private final Map<String, Object> attributeMap = new HashMap<String, 
Object>();
+    private final String sessionID = UUID.randomUUID().toString();
+    private final long creationTime = System.currentTimeMillis();
+    private boolean invalidated = false;
+    private int maxActiveInterval = 1800;
+    
+    public HttpSessionImpl(final ServletContext context) {
+        this.servletContext = context;
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+    
+    @Override
+    public Object getAttribute(final String name) {
+        checkInvalidatedState();
+        return this.attributeMap.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        checkInvalidatedState();
+        return Collections.enumeration(this.attributeMap.keySet());
+    }
+
+    @Override
+    public String getId() {
+        return this.sessionID;
+    }
+
+    @Override
+    public long getCreationTime() {
+        checkInvalidatedState();
+        return this.creationTime;
+    }
+
+    @Override
+    public Object getValue(final String name) {
+        checkInvalidatedState();
+        return getAttribute(name);
+    }
+
+    @Override
+    public String[] getValueNames() {
+        checkInvalidatedState();
+        return this.attributeMap.keySet().toArray(new 
String[this.attributeMap.keySet().size()]);
+    }
+
+    @Override
+    public void putValue(final String name, final Object value) {
+        checkInvalidatedState();
+        setAttribute(name, value);
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        checkInvalidatedState();
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void removeValue(final String name) {
+        checkInvalidatedState();
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object value) {
+        checkInvalidatedState();
+        this.attributeMap.put(name, value);
+    }
+
+    @Override
+    public void invalidate() {
+        checkInvalidatedState();
+        this.invalidated = true;
+    }
+    
+    private void checkInvalidatedState() {
+        if (invalidated) {
+            throw new IllegalStateException("Session is already invalidated.");
+        }
+    }
+    
+    public boolean isInvalidated() {
+        return invalidated;
+    }
+
+    @Override
+    public boolean isNew() {
+        checkInvalidatedState();
+        return true;
+    }
+    
+    @Override
+    public long getLastAccessedTime() {
+        checkInvalidatedState();
+        return creationTime;
+    }
+
+    @Override
+    public int getMaxInactiveInterval() {
+        return maxActiveInterval;
+    }
+
+    @Override
+    public void setMaxInactiveInterval(final int interval) {
+        this.maxActiveInterval = interval;
+    }
+
+    // --- unsupported operations ---
+    @Override
+    @SuppressWarnings("deprecation")
+    public javax.servlet.http.HttpSessionContext getSessionContext() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/RequestParameterImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestParameterImpl.java
new file mode 100644
index 0000000..4d99a60
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestParameterImpl.java
@@ -0,0 +1,92 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sling.api.request.RequestParameter;
+
+/**
+ * Implementation of {@link RequestParameter}.
+ */
+public class RequestParameterImpl implements RequestParameter {
+
+    private static final String CONTENT_TYPE = "text/plain";
+
+    private final String name;
+    private final String value;
+
+    public RequestParameterImpl(final String name, final String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public byte[] get() {
+        return this.value.getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public String getContentType() {
+        return CONTENT_TYPE;
+    }
+
+    @Override
+    public InputStream getInputStream() {
+        return new ByteArrayInputStream(this.get());
+    }
+
+    @Override
+    public String getFileName() {
+        return null;
+    }
+
+    @Override
+    public long getSize() {
+        return this.get().length;
+    }
+
+    @Override
+    public String getString() {
+        return this.value;
+    }
+
+    @Override
+    public String getString(final String encoding) throws 
UnsupportedEncodingException {
+        return new String(this.get(), encoding);
+    }
+
+    @Override
+    public boolean isFormField() {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return this.getString();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/RequestParameterMapImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestParameterMapImpl.java
new file mode 100644
index 0000000..32a3887
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestParameterMapImpl.java
@@ -0,0 +1,56 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Implementation of {@link RequestParameterMap}.
+ */
+public class RequestParameterMapImpl extends LinkedHashMap<String, 
RequestParameter[]>
+    implements RequestParameterMap {
+    
+    public RequestParameterMapImpl(@NotNull final Map<String, String[]> 
params) {
+        for(final Map.Entry<String, String[]> entry : params.entrySet()) {
+            final RequestParameter[] values = new 
RequestParameter[entry.getValue().length];
+            int index = 0;
+            for(final String v : entry.getValue()) {
+                values[index] = new RequestParameterImpl(entry.getKey(), v);
+                index++;
+            }
+            this.put(entry.getKey(), values);
+        }
+    }
+
+    @Override
+    public RequestParameter getValue(final String name) {
+        RequestParameter[] params = getValues(name);
+        return (params != null && params.length > 0) ? params[0] : null;
+    }
+
+    @Override
+    public RequestParameter[] getValues(final String name) {
+        return this.get(name);
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/RequestPathInfoImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestPathInfoImpl.java
new file mode 100644
index 0000000..1181696
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestPathInfoImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.api.request.builder.impl;
+
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * {@link RequestPathInfo} implementation.
+ */
+public class RequestPathInfoImpl implements RequestPathInfo {
+
+    private final String extension;
+    private final String resourcePath;
+    private final String[] selectors;
+    private final String suffix;
+    
+    private final ResourceResolver resourceResolver;
+    
+    public RequestPathInfoImpl(final Resource resource,
+        final String[] selectors,
+        final String extension,
+        final String suffix) {
+        this.resourceResolver = resource.getResourceResolver();
+        this.resourcePath = resource.getPath();
+        this.selectors = selectors == null ? new String[0] : selectors;
+        this.extension = extension;
+        this.suffix = suffix;
+    }
+
+    @Override
+    public String getExtension() {
+        return this.extension;
+    }
+
+    @Override
+    public String getResourcePath() {
+        return this.resourcePath;
+    }
+
+    @Override
+    public String[] getSelectors() {
+        return this.selectors;
+    }
+
+    @Override
+    public String getSelectorString() {
+        return this.selectors.length == 0 ? null : String.join(".", 
this.selectors);
+    }
+
+    @Override
+    public String getSuffix() {
+        return this.suffix;
+    }
+
+    @Override
+    public Resource getSuffixResource() {
+        if (suffix == null) {
+            return null;
+        }
+        return this.resourceResolver.getResource(suffix);
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/RequestProgressTrackerImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestProgressTrackerImpl.java
new file mode 100644
index 0000000..a0d7836
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/RequestProgressTrackerImpl.java
@@ -0,0 +1,71 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.sling.api.request.RequestProgressTracker;
+
+/**
+ * Internal {@link RequestProgressTracker} implementation.
+ */
+public class RequestProgressTrackerImpl implements RequestProgressTracker {
+
+    @Override
+    public void log(String message) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void log(String format, Object... args) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void startTimer(String timerName) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void logTimer(String timerName) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void logTimer(String timerName, String format, Object... args) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public Iterator<String> getMessages() {
+        return Collections.emptyIterator();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void done() {
+        // does nothing in this mock class
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/ServletContextImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/ServletContextImpl.java
new file mode 100644
index 0000000..7c4731b
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/ServletContextImpl.java
@@ -0,0 +1,306 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRegistration.Dynamic;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+/**
+ * Internal {@link ServletContext} implementation.
+ */
+public class ServletContextImpl implements ServletContext {
+
+    @Override
+    public String getMimeType(final String file) {
+        return "application/octet-stream";
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Object getAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletContext getContext(final String uriPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getContextPath() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getInitParameter(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMajorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMinorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getNamedDispatcher(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRealPath(final String pPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public URL getResource(final String pPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public InputStream getResourceAsStream(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<String> getResourcePaths(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServerInfo() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Servlet getServlet(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServletContextName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getServletNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<Servlet> getServlets() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final String msg) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final Exception exception, final String msg) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final String msg, final Throwable throwable) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object object) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean setInitParameter(final String name, final String value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final String 
className) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final Servlet servlet) 
{
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final Class<? extends 
Servlet> servletClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends Servlet> T createServlet(final Class<T> clazz) throws 
ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletRegistration getServletRegistration(final String 
servletName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, ? extends ServletRegistration> 
getServletRegistrations() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final 
String className) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final 
Filter filter) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final 
Class<? extends Filter> filterClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends Filter> T createFilter(final Class<T> clazz) throws 
ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration getFilterRegistration(final String filterName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setSessionTrackingModes(final Set<SessionTrackingMode> 
sessionTrackingModes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addListener(final String pClassName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EventListener> void addListener(final T listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addListener(final Class<? extends EventListener> 
listenerClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EventListener> T createListener(final Class<T> clazz) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void declareRoles(final String... roleNames) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getVirtualServerName() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/SlingHttpServletRequestImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/SlingHttpServletRequestImpl.java
new file mode 100644
index 0000000..9ca89dd
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/SlingHttpServletRequestImpl.java
@@ -0,0 +1,892 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListResourceBundle;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.ReadListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.request.builder.SlingHttpServletRequestBuilder;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Internal {@link SlingHttpServletRequest} implementation.
+ */
+public class SlingHttpServletRequestImpl extends SlingAdaptable 
+    implements SlingHttpServletRequest, SlingHttpServletRequestBuilder {
+
+    /** Default http method */
+    private static final String DEFAULT_METHOD = "GET";
+
+    /** Protocol */
+    private static final String SECURE_PROTOCOL = "https";
+    private static final String HTTP_PROTOCOL = "http";
+
+    static final String CHARSET_SEPARATOR = ";charset=";
+
+    private static final ResourceBundle EMPTY_RESOURCE_BUNDLE = new 
ListResourceBundle() {
+        @Override
+        protected Object[][] getContents() {
+            return new Object[0][0];
+        }
+    };
+
+    /** Required resource */
+    private final Resource resource;
+
+    /** Optional selectors */
+    private String[] selectors;
+
+    /** Optional extension */
+    private String extension;
+
+    /** HTTP method */
+    private String requestMethod = DEFAULT_METHOD;
+    
+    /** Optional content type / character encoding */
+    private String contentType;
+    private String characterEncoding;
+
+    /** Optional body */
+    private String body;
+
+    /** Is the builder locked? */
+    private boolean locked = false;
+
+    /** Parameters map */
+    private final Map<String, String[]> parameters = new LinkedHashMap<>();
+
+    /** Request path info */
+    private RequestPathInfoImpl requestPathInfo;
+    
+    /** Optional query string */
+    private String queryString;
+
+    /** The path info, calculated based on the provided resource */
+    private String pathInfo;
+
+    /** Attributes */
+    private final Dictionary<String, Object> attributeMap = new Hashtable<>();
+
+    /** On demand session */
+    private HttpSession session;
+
+    /** On demand request parameter map */
+    private RequestParameterMap requestParameterMap;
+
+    /** Headers */
+    private final HeaderSupport headerSupport = new HeaderSupport();
+    
+    /** Cookies */
+    private final Map<String, Cookie> cookies = new LinkedHashMap<>();
+
+    /** Request progress tracker */
+    private final RequestProgressTracker progressTracker = new 
RequestProgressTrackerImpl();
+
+    private HttpServletRequest sessionProvider;
+
+    private HttpServletRequest attributesProvider;
+
+    private ServletContext servletContext;
+
+    private SlingHttpServletRequest requestDispatcherProvider;
+
+    private boolean getInputStreamCalled;
+    private boolean getReaderCalled;
+
+    // the following fields are not settable atm
+    private final Locale locale = Locale.US;
+    private final String contextPath = "";
+    private final String scheme = HTTP_PROTOCOL;
+    private final String serverName = "localhost";
+    private final int serverPort = 80;
+    private String authType;
+    private String remoteUser;
+    private String remoteAddr;
+    private String remoteHost;
+    private int remotePort;
+    private String servletPath = "";
+    private String responseContentType;
+    
+    /** 
+     * Create a new request builder with the minimal information
+     * @param resource The resource
+     */
+    public SlingHttpServletRequestImpl(final @NotNull Resource resource) {
+        checkNotNull("resource", resource);
+        this.resource = resource;
+    }
+
+    private void checkLocked() {
+        if ( locked ) {
+            throw new IllegalStateException("The builder can't be reused. 
Create a new builder instead.");
+        }
+    }
+
+    private void checkNotNull(final String info, final Object candidate) {
+        if (candidate == null) {
+            throw new IllegalArgumentException(info.concat(" is null"));
+        }
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withRequestMethod(@NotNull  
String method) {
+        this.checkLocked();
+        this.checkNotNull("method", method);
+        this.requestMethod = method.toUpperCase();
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withContentType(final 
String type) {
+        this.checkLocked();
+        final int pos = 
type.indexOf(SlingHttpServletRequestImpl.CHARSET_SEPARATOR);
+        if ( pos != -1 ) {
+           this.contentType = type.substring(0, pos);
+           this.characterEncoding = type.substring(pos + 
SlingHttpServletRequestImpl.CHARSET_SEPARATOR.length());
+        } else {
+            this.contentType = type;
+        }
+
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withBody(final String 
content) {
+        this.checkLocked();
+        this.body = content;
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withSelectors(final String 
... selectors) {
+        this.checkLocked();
+        this.selectors = selectors;
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withExtension(final String 
extension) {
+        this.checkLocked();
+        this.extension = extension;
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withParameter(final 
@NotNull String key, final @NotNull String value) {
+        this.checkLocked();
+        this.checkNotNull("key", key);
+        this.checkNotNull("value", value);
+        this.parameters.put(key, new String[] {value});
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withParameter(final 
@NotNull String key, final @NotNull String[] values) {
+        this.checkLocked();
+        this.checkNotNull("key", key);
+        this.checkNotNull("values", values);
+        this.parameters.put(key, values);
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder withParameters(final 
@NotNull  Map<String, String[]> parameters) {
+        this.checkLocked();
+        this.checkNotNull("parameters", parameters);
+        this.parameters.putAll(parameters);
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder useAttributesFrom(@NotNull 
HttpServletRequest request) {
+        this.checkLocked();
+        this.checkNotNull("request", request);
+        this.attributesProvider = request;
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder 
useServletContextFrom(@NotNull HttpServletRequest request) {
+        this.checkLocked();
+        this.checkNotNull("request", request);
+        this.servletContext = request.getServletContext();
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder useSessionFrom(@NotNull 
HttpServletRequest request) {
+        this.checkLocked();
+        this.checkNotNull("request", request);
+        this.sessionProvider = request;
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequestBuilder 
useRequestDispatcherFrom(@NotNull SlingHttpServletRequest request) {
+        this.checkLocked();
+        this.checkNotNull("request", request);
+        this.requestDispatcherProvider = request;
+        return this;
+    }
+
+    @Override
+    public @NotNull SlingHttpServletRequest build() {
+        this.checkLocked();
+        this.locked = true;
+
+        this.requestPathInfo = new RequestPathInfoImpl(this.resource, 
this.selectors, this.extension, null);
+        this.queryString = this.formatQueryString();
+        this.pathInfo = this.buildPathInfo();
+
+        if ( this.servletContext == null ) {
+            this.servletContext = new ServletContextImpl();
+        }
+        if (this.body == null ) {
+            this.body = "";
+        }
+        return this;
+    }
+
+    private String buildPathInfo() {
+        final StringBuilder builder = new StringBuilder();
+
+        builder.append(this.requestPathInfo.getResourcePath());
+        if ( this.requestPathInfo.getSelectorString() != null ) {
+            builder.append('.');
+            builder.append(this.requestPathInfo.getSelectorString());
+        }
+
+        if ( this.requestPathInfo.getExtension() != null ) {
+            builder.append('.');
+            builder.append(this.requestPathInfo.getExtension());
+        }
+
+        if ( this.requestPathInfo.getSuffix() != null ) {
+            builder.append(this.requestPathInfo.getSuffix());
+        }
+
+        return builder.toString();
+    }
+
+    private String formatQueryString() {
+        final StringBuilder builder = new StringBuilder();
+        for (Map.Entry<String, String[]> entry : 
this.getParameterMap().entrySet()) {
+            if (entry.getValue() != null) {
+                formatQueryStringParameter(builder, entry);
+            }
+        }
+        return builder.length() > 0 ? builder.toString() : null;
+    }
+
+    private static String encode(final String v) {
+        try {
+            return URLEncoder.encode(v, StandardCharsets.UTF_8.name());
+        } catch (final UnsupportedEncodingException uee) {
+            // UTF-8 is always supported, we return the string as-is to make 
the compiler happy
+            return v;
+        }
+    }
+
+    private static void formatQueryStringParameter(final StringBuilder 
builder, final Map.Entry<String, String[]> entry) {
+        for (String value : entry.getValue()) {
+            if (builder.length() != 0) {
+                builder.append('&');
+            }
+            builder.append(encode(entry.getKey()));
+            builder.append('=');
+            if (value != null) {
+                builder.append(encode(value));
+            }
+        }
+    }
+
+    @Override
+    public Resource getResource() {
+        return this.resource;
+    }
+    
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return this.getResource().getResourceResolver();
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return getSession(true);
+    }
+
+    @Override
+    public HttpSession getSession(final boolean create) {
+        if (this.session == null && create) {
+            if ( this.sessionProvider != null ) {
+                this.session = this.sessionProvider.getSession(create);
+            } else {
+                this.session = new HttpSessionImpl(this.servletContext);
+           }
+        }
+        return this.session;
+    }
+
+    @Override
+    public RequestPathInfo getRequestPathInfo() {
+        return this.requestPathInfo;
+    }
+
+    @Override
+    public Object getAttribute(final String name) {
+        if ( this.attributesProvider != null ) {
+            return this.attributesProvider.getAttribute(name);
+        }
+        return this.attributeMap.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        if ( this.attributesProvider != null ) {
+            return this.attributesProvider.getAttributeNames();
+        }
+        return this.attributeMap.keys();
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        if ( this.attributesProvider != null ) {
+            this.attributesProvider.removeAttribute(name);
+        } else {
+            this.attributeMap.remove(name);
+        }
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object object) {
+        if ( this.attributesProvider != null ) {
+            this.attributesProvider.setAttribute(name, object);
+        } else {
+            this.attributeMap.put(name, object);
+        }
+    }
+
+    @Override
+    public String getParameter(final String name) {
+        final String[] values = this.parameters.get(name);
+        if (values != null && values.length > 0) {
+            return values[0];
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, String[]> getParameterMap() {
+        return Collections.unmodifiableMap(this.parameters);
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return Collections.enumeration(this.parameters.keySet());
+    }
+
+    @Override
+    public String[] getParameterValues(final String name) { 
+        return this.parameters.get(name);
+    }
+
+    @Override
+    public RequestParameter getRequestParameter(final String name) {
+        return this.getRequestParameterMap().getValue(name);
+    }
+
+    @Override
+    public RequestParameterMap getRequestParameterMap() {
+        if ( this.requestParameterMap == null ) {
+            this.requestParameterMap = new 
RequestParameterMapImpl(this.parameters);
+        }
+        return this.requestParameterMap;
+    }
+
+    @Override
+    public RequestParameter[] getRequestParameters(final String name) {
+        return this.getRequestParameterMap().get(name);
+    }
+
+    @Override
+    public List<RequestParameter> getRequestParameterList() {
+        final List<RequestParameter> params = new 
ArrayList<RequestParameter>();
+        for (final RequestParameter[] requestParameters : 
getRequestParameterMap().values()) {
+            params.addAll(Arrays.asList(requestParameters));
+        }
+        return params;
+    }
+
+    @Override
+    public Collection<Part> getParts() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Part getPart(final String name) {
+        return null;
+    }
+
+    @Override
+    public Locale getLocale() {
+        return this.locale;
+    }
+
+    @Override
+    public Enumeration<Locale> getLocales() {
+        return Collections.enumeration(Collections.singleton(getLocale()));
+    }
+
+    @Override
+    public String getContextPath() {
+        return this.contextPath;
+    }
+
+    @Override
+    public String getQueryString() {
+        return this.queryString;
+    }
+
+    @Override
+    public String getScheme() {
+        return this.scheme;
+    }
+
+    @Override
+    public String getServerName() {
+        return this.serverName;
+    }
+
+    @Override
+    public int getServerPort() {
+        return this.serverPort;
+    }
+
+    @Override
+    public boolean isSecure() {
+        return SECURE_PROTOCOL.equals(this.scheme);
+    }
+
+    @Override
+    public String getMethod() {
+        return this.requestMethod;
+    }
+
+    @Override
+    public long getDateHeader(final String name) {
+        return headerSupport.getDateHeader(name);
+    }
+
+    @Override
+    public String getHeader(final String name) {
+        return headerSupport.getHeader(name);
+    }
+
+    @Override
+    public Enumeration<String> getHeaderNames() {
+        return Collections.enumeration(headerSupport.getHeaderNames());
+    }
+
+    @Override
+    public Enumeration<String> getHeaders(final String name) {
+        return Collections.enumeration(headerSupport.getHeaders(name));
+    }
+ 
+    @Override
+    public int getIntHeader(final String name) {
+        return headerSupport.getIntHeader(name);
+    }
+
+    @Override
+    public Cookie getCookie(final String name) {
+        return this.cookies.get(name);
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        if ( this.cookies.isEmpty() ) {
+            return null;
+        }
+        return cookies.values().toArray(new Cookie[cookies.size()]);
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(final Locale locale) {
+        return getResourceBundle(null, locale);
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(final String baseName, final 
Locale locale) {
+        return EMPTY_RESOURCE_BUNDLE;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return this.characterEncoding;
+    }
+
+    @Override
+    public void setCharacterEncoding(final String encoding) throws 
UnsupportedEncodingException {
+        this.characterEncoding = encoding;
+    }
+
+    @Override
+    public String getContentType() {
+        if (this.contentType == null) {
+            return null;
+        } else if ( this.characterEncoding == null ) {
+            return this.contentType;
+        }
+        return 
this.contentType.concat(CHARSET_SEPARATOR).concat(this.characterEncoding);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() {
+        if (getReaderCalled) {
+            throw new IllegalStateException();
+        }
+        getInputStreamCalled = true;
+        return new ServletInputStream() {
+            private final InputStream is = new 
ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
+
+            @Override
+            public int read() throws IOException {
+                return is.read();
+            }
+
+            @Override
+            public boolean isReady() {
+                return true;
+            }
+
+            @Override
+            public boolean isFinished() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public BufferedReader getReader() {
+        if (getInputStreamCalled) {
+            throw new IllegalStateException();
+        }
+        getReaderCalled = true;
+        return new BufferedReader(new StringReader(this.body));
+    }
+
+    @Override
+    public int getContentLength() {
+        return this.body.length();
+    }
+    
+    @Override
+    public long getContentLengthLong() {
+        return this.getContentLength();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final String path) {
+        if ( this.requestDispatcherProvider != null ) {
+            return this.requestDispatcherProvider.getRequestDispatcher(path);
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final String path, final 
RequestDispatcherOptions options) {
+        if ( this.requestDispatcherProvider != null ) {
+            return this.requestDispatcherProvider.getRequestDispatcher(path, 
options);
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final Resource resource) {
+        if ( this.requestDispatcherProvider != null ) {
+            return 
this.requestDispatcherProvider.getRequestDispatcher(resource);
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final Resource resource, 
final RequestDispatcherOptions options) {
+        if ( this.requestDispatcherProvider != null ) {
+            return 
this.requestDispatcherProvider.getRequestDispatcher(resource, options);
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRemoteUser() {
+        return remoteUser;
+    }
+
+    @Override
+    public String getRemoteAddr() {
+        return remoteAddr;
+    }
+
+    @Override
+    public String getRemoteHost() {
+        return remoteHost;
+    }
+
+    @Override
+    public int getRemotePort() {
+        return remotePort;
+    }
+
+    @Override
+    public String getServletPath() {
+        return this.servletPath;
+    }
+
+    @Override
+    public String getPathInfo() {        
+        return this.pathInfo;
+    }
+
+    @Override
+    public String getRequestURI() {
+        final StringBuilder requestUri = new StringBuilder();
+        requestUri.append(this.contextPath);
+        requestUri.append(this.servletPath);
+        requestUri.append(this.pathInfo);
+        return requestUri.toString();
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        final StringBuffer requestUrl = new StringBuffer();
+
+        requestUrl.append(this.scheme);
+        requestUrl.append("://");
+        requestUrl.append(this.serverName);
+        boolean includePort = true;
+        if ( (HTTP_PROTOCOL.equals(this.scheme) && this.serverPort == 80 )
+             || (SECURE_PROTOCOL.equals(this.scheme) && this.serverPort == 
443) ) {
+            includePort = false;
+        }
+        if ( includePort ) {
+            requestUrl.append(':');
+            requestUrl.append(this.serverPort);
+        }
+        requestUrl.append(getRequestURI());
+
+        return requestUrl;
+    }
+
+    @Override
+    public String getAuthType() {
+        return this.authType;
+    }
+
+    @Override
+    public String getResponseContentType() {
+        return responseContentType;
+    }
+
+    @Override
+    public Enumeration<String> getResponseContentTypes() {
+        return 
Collections.enumeration(Collections.singleton(responseContentType));
+    }
+
+    @Override
+    public RequestProgressTracker getRequestProgressTracker() {
+        return this.progressTracker;
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+
+    // --- unsupported operations ---
+
+    @Override
+    public String getPathTranslated() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestedSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromUrl() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocalAddr() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocalName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getLocalPort() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getProtocol() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRealPath(String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean authenticate(HttpServletResponse response) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void login(String pUsername, String password) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void logout() throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext startAsync() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest, 
ServletResponse servletResponse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAsyncStarted() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAsyncSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DispatcherType getDispatcherType() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String changeSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) 
throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/impl/SlingHttpServletResponseImpl.java
 
b/src/main/java/org/apache/sling/api/request/builder/impl/SlingHttpServletResponseImpl.java
new file mode 100644
index 0000000..5ebeec6
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/api/request/builder/impl/SlingHttpServletResponseImpl.java
@@ -0,0 +1,387 @@
+/*
+ * 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.api.request.builder.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.apache.sling.api.request.builder.SlingHttpServletResponseBuilder;
+import org.apache.sling.api.request.builder.SlingHttpServletResponseResult;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Internal {@link SlingHttpServletResponse} implementation.
+ */
+public class SlingHttpServletResponseImpl 
+    extends SlingAdaptable 
+    implements SlingHttpServletResponseResult, SlingHttpServletResponseBuilder 
{
+
+    /** Headers */
+    private final HeaderSupport headerSupport = new HeaderSupport();
+    
+    /** Cookies */
+    private final Map<String, Cookie> cookies = new LinkedHashMap<>();
+
+    private String contentType;
+
+    private String characterEncoding;
+
+    private Locale locale = Locale.US;
+
+    private long contentLength;
+
+    private int status;
+
+    private boolean isCommitted;
+
+    private String statusMessage;
+
+    private int bufferSize = 8192;
+
+    private ByteArrayOutputStream outputStream;
+    
+    private ServletOutputStream servletOutputStream;
+    
+    private PrintWriter printWriter;
+    
+    /** Is the builder locked? */
+    private boolean locked = false;
+
+    private void checkLocked() {
+        if ( locked ) {
+            throw new IllegalStateException("The builder can't be reused. 
Create a new builder instead.");
+        }
+    }
+
+    @Override
+    public @NotNull SlingHttpServletResponseResult build() {
+        this.checkLocked();
+        this.locked = true;
+        this.reset();
+        return this;
+    }
+
+    private void checkCommitted() {
+        if (isCommitted()) {
+            throw new IllegalStateException("Response already committed.");
+        }
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return this.characterEncoding;
+    }
+
+    @Override
+    public void setCharacterEncoding(final String encoding) {
+        this.characterEncoding = encoding;
+    }
+
+    @Override
+    public String getContentType() {
+        if (this.contentType == null) {
+            return null;
+        } else if ( this.characterEncoding == null ) {
+            return this.contentType;
+        }
+        return 
this.contentType.concat(SlingHttpServletRequestImpl.CHARSET_SEPARATOR).concat(this.characterEncoding);
+    }
+
+    @Override
+    public void setContentType(final String type) {
+        final int pos = 
type.indexOf(SlingHttpServletRequestImpl.CHARSET_SEPARATOR);
+        if ( pos != -1 ) {
+           this.contentType = type.substring(0, pos);
+           this.characterEncoding = type.substring(pos + 
SlingHttpServletRequestImpl.CHARSET_SEPARATOR.length());
+        } else {
+            this.contentType = type;
+        }
+    }
+
+    @Override
+    public void setContentLength(final int len) {
+        this.contentLength = len;
+    }
+
+    @Override
+    public void setContentLengthLong(final long len) {
+        this.contentLength = len;
+    }
+
+    @Override
+    public void setStatus(final int sc, final String message) {
+        setStatus(sc);
+        this.statusMessage = message;
+    }
+
+    @Override
+    public void setStatus(final int sc) {
+        this.status = sc;
+    }
+
+    @Override
+    public int getStatus() {
+        return this.status;
+    }
+
+    @Override
+    public void sendError(final int sc, final String msg) {
+        this.setStatus(sc);
+        this.statusMessage = msg;
+        this.isCommitted = true;
+    }
+
+    @Override
+    public void sendError(final int sc) {
+        this.setStatus(sc);
+        this.statusMessage = null;
+        this.isCommitted = true;
+    }
+
+    @Override
+    public void sendRedirect(final String location) {
+        this.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+        this.statusMessage = null;
+        this.setHeader("Location", location);
+        this.isCommitted = true;
+    }
+
+    @Override
+    public void addHeader(final String name, final String value) {
+        this.headerSupport.addHeader(name, value);
+    }
+
+    @Override
+    public void addIntHeader(final String name, final int value) {
+        this.headerSupport.addIntHeader(name, value);
+    }
+
+    @Override
+    public void addDateHeader(final String name, final long date) {
+        this.headerSupport.addDateHeader(name, date);
+    }
+
+    @Override
+    public void setHeader(final String name, final String value) {
+        this.headerSupport.setHeader(name, value);
+    }
+
+    @Override
+    public void setIntHeader(final String name, final int value) {
+        this.headerSupport.setIntHeader(name, value);
+    }
+
+    @Override
+    public void setDateHeader(final String name, final long date) {
+        this.headerSupport.setDateHeader(name, date);
+    }
+
+    @Override
+    public boolean containsHeader(final String name) {
+        return this.headerSupport.containsHeader(name);
+    }
+
+    @Override
+    public String getHeader(final String name) {
+        return this.headerSupport.getHeader(name);
+    }
+
+    @Override
+    public Collection<String> getHeaders(final String name) {
+        return this.headerSupport.getHeaders(name);
+    }
+
+    @Override
+    public Collection<String> getHeaderNames() {
+        return this.headerSupport.getHeaderNames();
+    }
+
+    private Charset getCharset() {
+        if ( this.characterEncoding == null ) {
+            return StandardCharsets.UTF_8;
+        }
+        return Charset.forName(this.characterEncoding);
+    }
+
+    @Override
+    public PrintWriter getWriter() {
+        if (this.printWriter == null) {
+            this.printWriter = new PrintWriter(new 
OutputStreamWriter(getOutputStream(), getCharset()));
+        }
+        return this.printWriter;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() {
+        if (this.servletOutputStream == null) {
+            this.servletOutputStream = new ServletOutputStream() {
+                @Override
+                public void write(int b) throws IOException {
+                    outputStream.write(b);
+                }
+
+                @Override
+                public boolean isReady() {
+                    return true;
+                }
+                
+                @Override
+                public void setWriteListener(final WriteListener 
writeListener) {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+        return this.servletOutputStream;
+    }
+
+    @Override
+    public void reset() {
+        this.checkCommitted();
+        this.cookies.clear();
+        this.headerSupport.reset();
+        this.status = HttpServletResponse.SC_OK;
+        this.contentLength = -1L;
+        this.statusMessage = null;
+        this.resetBuffer();
+    }
+
+    @Override
+    public void resetBuffer() {
+        this.checkCommitted();
+        this.outputStream = new ByteArrayOutputStream();
+        this.servletOutputStream = null;
+        this.printWriter = null;
+    }
+
+    @Override
+    public int getBufferSize() {
+        return this.bufferSize;
+    }
+
+    @Override
+    public void setBufferSize(final int size) {
+        this.bufferSize = size;
+    }
+
+    @Override
+    public void flushBuffer() {
+        this.isCommitted = true;
+    }
+
+    @Override
+    public boolean isCommitted() {
+        return isCommitted;
+    }
+
+    @Override
+    public void addCookie(final Cookie cookie) {
+        this.cookies.put(cookie.getName(), cookie);
+    }
+
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    @Override
+    public void setLocale(final Locale loc) {
+        this.locale = loc;
+    }
+
+    @Override
+    public long getContentLength() {
+        return this.contentLength;
+    }    
+
+    @Override
+    public String getStatusMessage() {
+        return this.statusMessage;
+    }
+
+    @Override
+    public Cookie getCookie(final String name) {
+        return this.cookies.get(name);
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        if ( this.cookies.isEmpty() ) {
+            return null;
+        }
+        return cookies.values().toArray(new Cookie[cookies.size()]);
+    }
+
+    @Override
+    public byte[] getOutput() {
+        this.isCommitted = true;
+        if (printWriter != null) {
+            printWriter.flush();
+        }
+        if (servletOutputStream != null) {
+            try {
+                servletOutputStream.flush();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+        return outputStream.toByteArray();
+    }
+
+    @Override
+    public String getOutputAsString() {
+        this.isCommitted = true;
+        return new String(getOutput(), this.getCharset());
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public String encodeRedirectUrl(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeRedirectURL(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeUrl(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeURL(String url) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/api/request/builder/package-info.java 
b/src/main/java/org/apache/sling/api/request/builder/package-info.java
new file mode 100644
index 0000000..11251e6
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/request/builder/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
[email protected]("1.0")
+package org.apache.sling.api.request.builder;
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/BuildersTest.java 
b/src/test/java/org/apache/sling/api/request/builder/BuildersTest.java
new file mode 100644
index 0000000..35c0571
--- /dev/null
+++ b/src/test/java/org/apache/sling/api/request/builder/BuildersTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.api.request.builder;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class BuildersTest {
+
+    @Test(expected = IllegalArgumentException.class) 
+    public void createRequestBuilderNullResource() {
+        Builders.newRequestBuilder(null);
+    }
+
+    @Test 
+    public void createRequestBuilder() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+
+        final SlingHttpServletRequestBuilder builder = 
Builders.newRequestBuilder(resource);
+        builder.withExtension("html").withSelectors("tidy", "json");
+
+        final SlingHttpServletRequest req = builder.build();
+        assertEquals(resource, req.getResource());
+        assertEquals(resolver, req.getResourceResolver());
+        assertEquals("html", req.getRequestPathInfo().getExtension());
+        assertEquals("/content/page", 
req.getRequestPathInfo().getResourcePath());
+        assertEquals("tidy.json", 
req.getRequestPathInfo().getSelectorString());
+        assertArrayEquals(new String[] {"tidy", "json"}, 
req.getRequestPathInfo().getSelectors());
+        assertNull(req.getRequestPathInfo().getSuffix());
+        assertNull(req.getRequestPathInfo().getSuffixResource());
+    }
+
+    @Test
+    public void createResponseBuilder() {
+        final SlingHttpServletResponseBuilder builder = 
Builders.newResponseBuilder();
+        final SlingHttpServletResponseResult result = builder.build();
+        assertNotNull(result);        
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/HeaderSupportTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/HeaderSupportTest.java
new file mode 100644
index 0000000..1bc7071
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/HeaderSupportTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class HeaderSupportTest {
+    
+    @Test public void testDateHeaders() {
+        final HeaderSupport support = new HeaderSupport();
+        // no header set, return -1
+        assertEquals(-1L, support.getDateHeader("date"));
+        // set/get date header as long
+        final long now = System.currentTimeMillis();
+        support.addDateHeader("date", now);
+        // precision is second (not millisecond)
+        assertEquals(now - (now % 1000), support.getDateHeader("date"));
+        assertNotNull(support.getHeader("date"));
+        // no need to test exact output of JDK formatter
+        assertTrue(support.getHeader("date").endsWith(" GMT"));
+        // wrong format
+        support.addHeader("nodate", "foo");
+        try {
+            support.getDateHeader("nodate");
+            fail();
+        } catch ( final IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test public void testIntDateHeaders() {
+        final HeaderSupport support = new HeaderSupport();
+        // no header set, return -1
+        assertEquals(-1L, support.getIntHeader("number"));
+        // set/get int header
+        support.addIntHeader("number", 5);
+        assertEquals(5, support.getIntHeader("number"));
+        assertEquals("5", support.getHeader("number"));
+        // wrong format
+        support.addHeader("nonumber", "foo");
+        try {
+            support.getIntHeader("nonumber");
+            fail();
+        } catch ( final NumberFormatException nfe) {
+            // expected
+        }
+    }
+
+    @Test public void testAddSetHeaders() {
+        final HeaderSupport support = new HeaderSupport();
+        support.addHeader("string", "a");
+        support.addHeader("string", "b");
+
+        support.addIntHeader("number", 3);
+        support.addIntHeader("number", 1);
+
+        support.addDateHeader("date", 300000000);
+        support.addDateHeader("date", 100000000);
+
+        assertEquals("a", support.getHeader("string"));
+        assertEquals(Arrays.asList("a", "b"), support.getHeaders("string"));
+
+        assertEquals("3", support.getHeader("number"));
+        assertEquals(Arrays.asList("3", "1"), support.getHeaders("number"));
+
+        assertEquals(300000000, support.getDateHeader("date"));
+        assertEquals(2, support.getHeaders("date").size());
+
+        support.setHeader("string", "c");
+        assertEquals("c", support.getHeader("string"));
+        assertEquals(Arrays.asList("c"), support.getHeaders("string"));
+
+        support.setIntHeader("number", 9);
+        assertEquals("9", support.getHeader("number"));
+        assertEquals(Arrays.asList("9"), support.getHeaders("number"));
+
+        support.setDateHeader("date", 900000000);
+        assertEquals(900000000, support.getDateHeader("date"));
+        assertEquals(1, support.getHeaders("date").size());
+
+        assertTrue(support.containsHeader("date"));
+        assertFalse(support.containsHeader("foo"));
+        
+        assertTrue(support.getHeaders("foo").isEmpty());
+
+        assertEquals(Arrays.asList("string", "number", "date"), new 
ArrayList<>(support.getHeaderNames()));
+
+        support.reset();
+        assertTrue(support.getHeaderNames().isEmpty());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/HttpSessionImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/HttpSessionImplTest.java
new file mode 100644
index 0000000..667860f
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/HttpSessionImplTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class HttpSessionImplTest {
+
+    @Test
+    public void testServletContext() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(null);
+        assertNull(httpSession.getServletContext());
+        httpSession = new HttpSessionImpl(new ServletContextImpl());
+        assertNotNull(httpSession.getServletContext());
+    }
+
+    @Test
+    public void testId() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        assertNotNull(httpSession.getId());
+    }
+
+    @Test
+    public void testCreationTime() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        assertNotNull(httpSession.getCreationTime());
+    }
+
+    @Test
+    public void testAttributes() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        httpSession.setAttribute("attr1", "value1");
+        assertTrue(httpSession.getAttributeNames().hasMoreElements());
+        assertEquals("value1", httpSession.getAttribute("attr1"));
+        httpSession.removeAttribute("attr1");
+        assertFalse(httpSession.getAttributeNames().hasMoreElements());
+    }
+
+    @Test
+    public void testValues() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        httpSession.putValue("attr1", "value1");
+        assertEquals(1, httpSession.getValueNames().length);
+        assertEquals("value1", httpSession.getValue("attr1"));
+        httpSession.removeValue("attr1");
+        assertEquals(0, httpSession.getValueNames().length);
+    }
+
+    @Test
+    public void testInvalidate() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        httpSession.invalidate();
+        assertTrue(httpSession.isInvalidated());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInvalidateStateCheck() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        httpSession.invalidate();
+        httpSession.getAttribute("attr1");
+    }
+
+    @Test
+    public void testIsNew() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        assertTrue(httpSession.isNew());
+   }
+
+    @Test
+    public void testGetLastAccessedTime() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        assertNotNull(httpSession.getLastAccessedTime());
+    }
+
+    @Test
+    public void testGetMaxInactiveInterval() {
+        HttpSessionImpl httpSession = new HttpSessionImpl(new 
ServletContextImpl());
+        assertTrue(httpSession.getMaxInactiveInterval() > 0);
+        httpSession.setMaxInactiveInterval(123);
+        assertEquals(123, httpSession.getMaxInactiveInterval());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/RequestParameterImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestParameterImplTest.java
new file mode 100644
index 0000000..43d4feb
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestParameterImplTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Test;
+
+public class RequestParameterImplTest {
+    
+    @Test public void testParameter() throws UnsupportedEncodingException {
+        final RequestParameterImpl param = new RequestParameterImpl("foo", 
"bar");
+        assertEquals("foo", param.getName());
+        assertEquals("bar", param.getString());
+        assertArrayEquals("bar".getBytes(StandardCharsets.UTF_8), param.get());
+        assertEquals("text/plain", param.getContentType());
+        assertNotNull(param.getInputStream());
+        assertNull(param.getFileName());
+        assertEquals(3L, param.getSize());
+        assertEquals("bar", param.getString("UTF-8"));
+        assertTrue(param.isFormField());
+        assertEquals(param.getString(), param.toString());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/RequestParameterMapImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestParameterMapImplTest.java
new file mode 100644
index 0000000..366906e
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestParameterMapImplTest.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.api.request.builder.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class RequestParameterMapImplTest {
+
+    @Test public void testMap() {
+        final Map<String, String[]> initial = new HashMap<>();
+        initial.put("foo", new String[] {"bar"});
+        initial.put("a", new String[] {"b", "c"});
+
+        final RequestParameterMapImpl map = new 
RequestParameterMapImpl(initial);
+        assertEquals(2, map.size());
+        assertTrue(map.containsKey("foo"));
+        assertTrue(map.containsKey("a"));
+
+        assertEquals("bar", map.getValue("foo").getString());
+        assertEquals("b", map.getValue("a").getString());
+        assertNull(map.getValue("unknown"));
+        assertEquals(2, map.getValues("a").length);
+        assertEquals("b", map.getValues("a")[0].getString());
+        assertEquals("c", map.getValues("a")[1].getString());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/RequestPathInfoImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestPathInfoImplTest.java
new file mode 100644
index 0000000..3450f0c
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestPathInfoImplTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class RequestPathInfoImplTest {
+
+    @Test
+    public void testExtension() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+
+        RequestPathInfo requestPathInfo = new RequestPathInfoImpl(resource, 
null, null, null);
+        assertNull(requestPathInfo.getExtension());
+        requestPathInfo = new RequestPathInfoImpl(resource, null, "ext", null);
+        assertEquals("ext", requestPathInfo.getExtension());
+    }
+
+    @Test
+    public void testResourcePath() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+
+        RequestPathInfo requestPathInfo = new RequestPathInfoImpl(resource, 
null, null, null);
+        assertEquals("/content/page", requestPathInfo.getResourcePath());
+    }
+
+    @Test
+    public void testSelector() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+
+        RequestPathInfo requestPathInfo = new RequestPathInfoImpl(resource, 
null, null, null);
+        assertNull(requestPathInfo.getSelectorString());
+        assertEquals(0, requestPathInfo.getSelectors().length);
+        
+        requestPathInfo = new RequestPathInfoImpl(resource, new String[] 
{"aa", "bb"}, null, null);
+        assertEquals("aa.bb", requestPathInfo.getSelectorString());
+        assertArrayEquals(new String[] { "aa", "bb" }, 
requestPathInfo.getSelectors());
+    }
+
+    @Test
+    public void testSuffix() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+
+        RequestPathInfo requestPathInfo = new RequestPathInfoImpl(resource, 
null, null, null);
+        assertNull(requestPathInfo.getSuffix());
+        
+        requestPathInfo = new RequestPathInfoImpl(resource, null, null, 
"/suffix");
+        assertEquals("/suffix", requestPathInfo.getSuffix());
+    }
+
+    @Test
+    public void testGetSuffixResource() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+        final Resource suffixResource = Mockito.mock(Resource.class);
+        
Mockito.when(resolver.getResource("/suffix")).thenReturn(suffixResource);
+
+        RequestPathInfo requestPathInfo = new RequestPathInfoImpl(resource, 
null, null, null);
+        assertNull(requestPathInfo.getSuffixResource());
+        
+        requestPathInfo = new RequestPathInfoImpl(resource, null, null, 
"/suffix");        
+        assertSame(suffixResource, requestPathInfo.getSuffixResource());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/RequestProgressTrackerImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestProgressTrackerImplTest.java
new file mode 100644
index 0000000..c757ba2
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/RequestProgressTrackerImplTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertFalse;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.junit.Test;
+
+public class RequestProgressTrackerImplTest {
+
+    @Test public void testTracker() {
+        final RequestProgressTrackerImpl tracker = new 
RequestProgressTrackerImpl();
+        // this test basically checks that all methods are callable
+        tracker.log("message");
+        tracker.log("%s", "message");
+        tracker.startTimer("foo");
+        tracker.logTimer("foo");
+        tracker.logTimer("foo", "%s", "msg");
+        assertFalse(tracker.getMessages().hasNext());
+        tracker.dump(new PrintWriter(new StringWriter()));
+        tracker.done();
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/ServletContextImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/ServletContextImplTest.java
new file mode 100644
index 0000000..a256627
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/ServletContextImplTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.EventListener;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+
+import org.junit.Test;
+
+public class ServletContextImplTest {
+
+    @Test public void testContext() throws ServletException {
+        final ServletContextImpl context = new ServletContextImpl();
+        // the context is very useless, it throws an exception for most methods
+        assertEquals("application/octet-stream", context.getMimeType("file"));
+        try {
+            context.getAttribute("name");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getAttributeNames();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getContext("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getContextPath();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getInitParameter("name");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getInitParameterNames();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getMajorVersion();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getMinorVersion();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getNamedDispatcher("name");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getRealPath("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getRequestDispatcher("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getResource("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getResourceAsStream("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getResourcePaths("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServerInfo();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServlet("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServletContextName();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServletNames();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServlets();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.log("msg");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.log("msg", new Exception());
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.log(new Exception(), "msg");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.removeAttribute("name");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.setAttribute("name", "value");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getEffectiveMajorVersion();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getEffectiveMinorVersion();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.setInitParameter("name", "value");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addServlet("name", "classname");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addServlet("name", new HttpServlet(){
+                
+            });
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addServlet("name", HttpServlet.class);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addFilter("name", "classname");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addFilter("name", new Filter(){
+
+                @Override
+                public void init(FilterConfig filterConfig) throws 
ServletException {
+                }
+
+                @Override
+                public void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain)
+                        throws IOException, ServletException {
+                }
+
+                @Override
+                public void destroy() {
+                }
+                
+            });
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addFilter("name", Filter.class);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.createServlet(HttpServlet.class);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServletRegistration("name");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getServletRegistrations();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.createFilter(Filter.class);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getFilterRegistration("name");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getFilterRegistrations();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getSessionCookieConfig();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.setSessionTrackingModes(null);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getDefaultSessionTrackingModes();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getEffectiveSessionTrackingModes();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addListener(new EventListener(){
+                
+            });
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.addListener(EventListener.class);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.createListener(EventListener.class);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getJspConfigDescriptor();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getClassLoader();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.declareRoles("a");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            context.getVirtualServerName();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/SlingHttpServletRequestImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/SlingHttpServletRequestImplTest.java
new file mode 100644
index 0000000..49c3a37
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/SlingHttpServletRequestImplTest.java
@@ -0,0 +1,520 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class SlingHttpServletRequestImplTest {
+    
+    private SlingHttpServletRequestImpl req;
+
+    private Resource resource;
+
+    @Before
+    public void setup() {
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        this.resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getPath()).thenReturn("/content/page");
+        Mockito.when(resource.getResourceResolver()).thenReturn(resolver);
+
+        this.req = new SlingHttpServletRequestImpl(resource);
+    }
+
+    @Test(expected = IllegalStateException.class) public void 
testCheckLocked() {
+        req.build();
+        req.withExtension("foo");
+    }
+
+    @Test public void testGetResource() {
+        req.build();
+        assertEquals(resource, req.getResource());
+        assertEquals(resource.getResourceResolver(), 
req.getResourceResolver());
+    }
+
+    @Test public void testGetRequestPathInfo() {
+        req.withExtension("html").withSelectors("tidy", "json");
+        req.build();
+        assertEquals("html", req.getRequestPathInfo().getExtension());
+        assertEquals("/content/page", 
req.getRequestPathInfo().getResourcePath());
+        assertEquals("tidy.json", 
req.getRequestPathInfo().getSelectorString());
+        assertArrayEquals(new String[] {"tidy", "json"}, 
req.getRequestPathInfo().getSelectors());
+        assertNull(req.getRequestPathInfo().getSuffix());
+        assertNull(req.getRequestPathInfo().getSuffixResource());
+    }
+
+    @Test public void testInternalSession() {
+        req.build();
+        assertNull(req.getSession(false));
+        final HttpSession s = req.getSession();
+        assertNotNull(s);
+        assertSame(s, req.getSession(true));
+        assertTrue(s instanceof HttpSessionImpl);
+    }
+
+    @Test public void testProvidedSession() {
+        final SlingHttpServletRequest outer = 
Mockito.mock(SlingHttpServletRequest.class);
+        final HttpSession outerSession = Mockito.mock(HttpSession.class);
+        Mockito.when(outer.getSession()).thenReturn(outerSession);
+        Mockito.when(outer.getSession(true)).thenReturn(outerSession);
+        Mockito.when(outerSession.getId()).thenReturn("provided");
+
+        req.useSessionFrom(outer).build();
+        assertNull(req.getSession(false));
+        final HttpSession s = req.getSession();
+        assertNotNull(s);
+        assertSame(s, req.getSession(true));
+        assertEquals("provided", s.getId());
+    }
+
+    @Test public void testInternalAttributes() {
+        req.build();
+        assertFalse(req.getAttributeNames().hasMoreElements());
+        req.setAttribute("a", "b");
+        assertEquals("b", req.getAttribute("a"));
+        assertEquals("a", req.getAttributeNames().nextElement());
+        req.removeAttribute("a");
+        assertFalse(req.getAttributeNames().hasMoreElements());
+        assertNull(req.getAttribute("a"));
+    }
+
+    @Test public void testProvidedAttributes() {
+        final SlingHttpServletRequest outer = 
Mockito.mock(SlingHttpServletRequest.class);
+
+        req.useAttributesFrom(outer).build();
+        req.getAttributeNames();
+        Mockito.verify(outer, Mockito.atLeastOnce()).getAttributeNames();
+        req.getAttribute("foo");
+        Mockito.verify(outer, Mockito.atLeastOnce()).getAttribute("foo");
+        req.removeAttribute("foo");
+        Mockito.verify(outer, Mockito.atLeastOnce()).removeAttribute("foo");
+        req.setAttribute("foo", "bar");
+        Mockito.verify(outer, Mockito.atLeastOnce()).setAttribute("foo", 
"bar");
+    }
+
+    @Test public void testParameters() {
+        req.withParameter("a", "b");
+        req.withParameter("c", new String[] {"d", "e"});
+        req.withParameters(Collections.singletonMap("f", new String[] {"g"}));
+        req.build();
+
+        assertEquals("b", req.getParameter("a"));
+        assertEquals("d", req.getParameter("c"));
+        assertEquals("g", req.getParameter("f"));
+        assertNull(req.getParameter("g"));
+    
+        final Map<String, String[]> params = req.getParameterMap();
+        assertEquals(3, params.size());
+        assertArrayEquals(new String[] {"b"}, params.get("a"));
+        assertArrayEquals(new String[] {"d", "e"}, params.get("c"));
+        assertArrayEquals(new String[] {"g"}, params.get("f"));
+
+        assertNotNull(req.getRequestParameter("a"));
+        assertNotNull(req.getRequestParameter("c"));
+        assertNotNull(req.getRequestParameter("f"));
+        assertNull(req.getRequestParameter("g"));
+    
+        assertEquals(1, req.getRequestParameters("a").length);
+        assertEquals(2, req.getRequestParameters("c").length);
+        assertEquals(1, req.getRequestParameters("f").length);
+        assertNull(req.getRequestParameters("g"));
+
+        final List<RequestParameter> list = req.getRequestParameterList();
+        assertEquals(4, list.size());
+
+        assertTrue(req.getParts().isEmpty());
+        assertNull(req.getPart("a"));
+    }
+
+    @Test public void testNoQueryString() {
+        req.build();
+        assertNull(req.getQueryString());
+    }
+
+    @Test public void testQueryString() {
+        req.withParameter("a", "b");
+        req.withParameter("c", new String[] {"d", "e"});
+        req.withParameters(Collections.singletonMap("f", new String[] {"g"}));
+        req.build();
+
+        assertEquals("a=b&c=d&c=e&f=g", req.getQueryString());
+    }
+
+    @Test public void testLocale() {
+        req.build();
+        assertEquals(Locale.US, req.getLocale());
+        assertEquals(Collections.singletonList(Locale.US), 
Collections.list(req.getLocales()));
+    }
+
+    @Test public void testGetContextPath() {
+        req.build();
+        assertEquals("", req.getContextPath());
+    }
+
+    @Test public void testGetScheme() {
+        req.build();
+        assertEquals("http", req.getScheme());
+    }
+
+    @Test public void testGetServerName() {
+        req.build();
+        assertEquals("localhost", req.getServerName());
+    }
+
+    @Test public void testGetServerPort() {
+        req.build();
+        assertEquals(80, req.getServerPort());
+    }
+
+    @Test public void testIsSecure() {
+        req.build();
+        assertFalse(req.isSecure());
+    }
+
+    @Test public void testDefaultGetMethod() {
+        req.build();
+        assertEquals("GET", req.getMethod());
+    }
+
+    @Test public void testGetMethod() {
+        req.withRequestMethod("POST").build();
+        assertEquals("POST", req.getMethod());
+    }
+
+    @Test public void testHeaders() {
+        req.build();
+        assertFalse(req.getHeaderNames().hasMoreElements());
+        assertEquals(-1, req.getDateHeader("foo"));
+        assertEquals(-1, req.getIntHeader("foo"));
+        assertNull(req.getHeader("foo"));
+        assertFalse(req.getHeaders("foo").hasMoreElements());
+    }
+
+    @Test public void testCookies() {
+        req.build();
+        assertNull(req.getCookies());
+        assertNull(req.getCookie("name"));
+    }
+
+    @Test public void testGetResourceBundle() {
+        req.build();
+        assertNotNull(req.getResourceBundle(req.getLocale()));
+        assertNotNull(req.getResourceBundle("base", req.getLocale()));
+    }
+
+    @Test public void testGetCharacterEncoding() throws 
UnsupportedEncodingException {
+        req.build();
+        assertNull(req.getCharacterEncoding());
+        req.setCharacterEncoding("UTF-8");
+        assertEquals("UTF-8", req.getCharacterEncoding());
+    }
+
+    @Test public void testDefaultContentType() throws 
UnsupportedEncodingException {
+        req.build();
+        assertNull(req.getContentType());
+    }
+
+    @Test public void testContentType() throws UnsupportedEncodingException {
+        req.withContentType("text/text").build();
+        assertEquals("text/text", req.getContentType());
+        assertNull(req.getCharacterEncoding());
+        req.setCharacterEncoding("UTF-8");
+        assertEquals("UTF-8", req.getCharacterEncoding());
+        assertEquals("text/text;charset=UTF-8", req.getContentType());
+    }
+
+    @Test public void testContentTypeAndCharset() throws 
UnsupportedEncodingException {
+        req.withContentType("text/text;charset=UTF-16").build();
+        assertEquals("text/text;charset=UTF-16", req.getContentType());
+        assertEquals("UTF-16", req.getCharacterEncoding());
+        req.setCharacterEncoding("UTF-8");
+        assertEquals("UTF-8", req.getCharacterEncoding());
+        assertEquals("text/text;charset=UTF-8", req.getContentType());
+    }
+
+    @Test public void testNoBody() {
+        req.build();
+        assertEquals(0, req.getContentLength());
+        assertEquals(0L, req.getContentLengthLong());
+    }
+
+    @Test public void testBodyReader() throws IOException {
+        req.withBody("body").build();
+        assertEquals(4, req.getContentLength());
+        assertEquals(4L, req.getContentLengthLong());
+        final Reader r = req.getReader();
+        try {
+            req.getInputStream();
+            fail();
+        } catch ( final IllegalStateException iae) {}
+        final char[] cbuf = new char[96];
+        final int l = r.read(cbuf);
+        assertEquals("body", new String(cbuf, 0, l));
+    }
+
+    @Test public void testBodyInputStream() throws IOException {
+        req.withBody("body").build();
+        assertEquals(4, req.getContentLength());
+        assertEquals(4L, req.getContentLengthLong());
+        final InputStream in = req.getInputStream();
+        try {
+            req.getReader();
+            fail();
+        } catch ( final IllegalStateException iae) {}
+        final byte[] buf = new byte[96];
+        final int l = in.read(buf);
+        assertEquals("body", new String(buf, 0, l));
+    }
+
+    @Test public void testDefaultRequestDispatcher() {
+        final Resource rsrc = Mockito.mock(Resource.class);
+        req.build();
+        try {
+            req.getRequestDispatcher("/path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getRequestDispatcher("/path", new RequestDispatcherOptions());
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getRequestDispatcher(rsrc);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getRequestDispatcher(rsrc, new RequestDispatcherOptions());
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+    }
+
+    @Test public void testProvidedRequestDispatcher() {
+        final Resource rsrc = Mockito.mock(Resource.class);
+        final RequestDispatcherOptions opts = new RequestDispatcherOptions();
+        final SlingHttpServletRequest outer = 
Mockito.mock(SlingHttpServletRequest.class);
+        req.useRequestDispatcherFrom(outer).build();
+
+        req.getRequestDispatcher("/path");
+        Mockito.verify(outer, Mockito.times(1)).getRequestDispatcher("/path");
+        req.getRequestDispatcher("/path", opts);
+        Mockito.verify(outer, Mockito.times(1)).getRequestDispatcher("/path", 
opts);
+        req.getRequestDispatcher(rsrc);
+        Mockito.verify(outer, Mockito.times(1)).getRequestDispatcher(rsrc);
+        req.getRequestDispatcher(rsrc, opts);
+        Mockito.verify(outer, Mockito.times(1)).getRequestDispatcher(rsrc, 
opts);
+    }
+
+    @Test public void testGetRemoteUser() {
+        req.build();
+        assertNull(req.getRemoteUser());
+    }
+
+    @Test public void testGetRemoteAddr() {
+        req.build();
+        assertNull(req.getRemoteAddr());
+    }
+
+    @Test public void testGetRemoteHost() {
+        req.build();
+        assertNull(req.getRemoteHost());
+    }
+
+    @Test public void testGetRemotePort() {
+        req.build();
+        assertEquals(0, req.getRemotePort());
+    }
+
+    @Test public void testGetServletPath() {
+        req.build();
+        assertEquals("", req.getServletPath());
+    }
+
+    @Test public void testGetPathInfo() {
+        req.build();
+        assertEquals("/content/page", req.getPathInfo());
+    }
+
+    @Test public void testGetRequestURI() {
+        req.build();
+        assertEquals("/content/page", req.getRequestURI());
+    }
+
+    @Test public void testGetRequestURL() {
+        req.build();
+        assertEquals("http://localhost/content/page";, 
req.getRequestURL().toString());
+    }
+
+    @Test public void testGetAuthType() {
+        req.build();
+        assertNull(req.getAuthType());
+    }
+
+    @Test public void getResponseContentType() {
+        req.build();
+        assertNull(req.getResponseContentType());
+        assertEquals(Collections.singletonList(null), 
Collections.list(req.getResponseContentTypes()));
+    }
+
+    @Test public void testGetRequestProgressTracker() {
+        req.build();
+        assertNotNull(req.getRequestProgressTracker());
+    }
+
+    @Test public void testDefaultServletContext() {
+        req.build();
+        final ServletContext ctx = req.getServletContext();
+        assertNotNull(ctx);
+        assertTrue(ctx instanceof ServletContextImpl);
+    }
+
+    @Test public void testProvidedServletContext() {
+        final SlingHttpServletRequest outer = 
Mockito.mock(SlingHttpServletRequest.class);
+        final ServletContext outerCtx = Mockito.mock(ServletContext.class);
+        Mockito.when(outer.getServletContext()).thenReturn(outerCtx);
+
+        req.useServletContextFrom(outer).build();
+        final ServletContext ctx = req.getServletContext();
+        assertNotNull(ctx);
+        Mockito.verify(outer, Mockito.times(1)).getServletContext();
+        assertFalse(ctx instanceof ServletContextImpl);
+    }
+
+    @Test public void testUnsupportedMethods() throws ServletException, 
IOException {
+        req.build();
+        try {
+            req.getPathTranslated();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getRequestedSessionId();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getUserPrincipal();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isRequestedSessionIdFromCookie();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isRequestedSessionIdFromURL();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isRequestedSessionIdFromUrl();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isRequestedSessionIdValid();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isUserInRole("foo");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getLocalAddr();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getLocalName();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getLocalPort();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getRealPath("path");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.authenticate(null);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.login("u", "p");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.logout();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.startAsync();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.startAsync(null, null);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isAsyncStarted();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.isAsyncSupported();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getAsyncContext();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.getDispatcherType();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.changeSessionId();
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            req.upgrade(null);
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/api/request/builder/impl/SlingHttpServletResponseImplTest.java
 
b/src/test/java/org/apache/sling/api/request/builder/impl/SlingHttpServletResponseImplTest.java
new file mode 100644
index 0000000..8543788
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/api/request/builder/impl/SlingHttpServletResponseImplTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.api.request.builder.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class SlingHttpServletResponseImplTest {
+    
+    private SlingHttpServletResponseImpl res;
+
+    @Before
+    public void setup() {
+        this.res = new SlingHttpServletResponseImpl();
+    }
+
+    @Test(expected = IllegalStateException.class) public void 
testCheckLocked() {
+        try {
+            res.build();
+        } catch ( final IllegalStateException error) {
+            fail();
+        }
+        res.build();
+    }
+
+
+    @Test public void testGetCharacterEncoding() throws 
UnsupportedEncodingException {
+        res.build();
+        assertNull(res.getCharacterEncoding());
+        res.setCharacterEncoding("UTF-8");
+        assertEquals("UTF-8", res.getCharacterEncoding());
+    }
+
+    @Test public void testDefaultContentType() throws 
UnsupportedEncodingException {
+        res.build();
+        assertNull(res.getContentType());
+    }
+
+    @Test public void testContentType() throws UnsupportedEncodingException {
+        res.build();
+        res.setContentType("text/text");
+        assertEquals("text/text", res.getContentType());
+        assertNull(res.getCharacterEncoding());
+        res.setCharacterEncoding("UTF-8");
+        assertEquals("UTF-8", res.getCharacterEncoding());
+        assertEquals("text/text;charset=UTF-8", res.getContentType());
+    }
+
+    @Test public void testContentTypeAndCharset() throws 
UnsupportedEncodingException {
+        res.build();
+        res.setContentType("text/text;charset=UTF-16");
+        assertEquals("text/text;charset=UTF-16", res.getContentType());
+        assertEquals("UTF-16", res.getCharacterEncoding());
+        res.setCharacterEncoding("UTF-8");
+        assertEquals("UTF-8", res.getCharacterEncoding());
+        assertEquals("text/text;charset=UTF-8", res.getContentType());
+    }
+
+    @Test public void testContentLength() {
+        res.build();
+        assertEquals(-1L, res.getContentLength());
+        res.setContentLength(500);
+        assertEquals(500L, res.getContentLength());
+        res.setContentLengthLong(5000L);
+        assertEquals(5000L, res.getContentLength());
+    }
+
+    @Test public void testSetStatus() {
+        res.build();
+        assertEquals(200, res.getStatus());
+        assertNull(res.getStatusMessage());
+
+        res.setStatus(201);
+        assertEquals(201, res.getStatus());
+        assertNull(res.getStatusMessage());
+
+        res.setStatus(202, "msg");
+        assertEquals(202, res.getStatus());
+        assertEquals("msg", res.getStatusMessage());
+
+        assertFalse(res.isCommitted());
+    }
+
+    @Test public void testSendError() {
+        res.build();
+        res.sendError(500);
+        assertEquals(500, res.getStatus());
+        assertNull(res.getStatusMessage());
+        assertTrue(res.isCommitted());
+    }
+
+    @Test public void testSendErrorWithMessage() {
+        res.build();
+        res.sendError(500, "msg");
+        assertEquals(500, res.getStatus());
+        assertEquals("msg", res.getStatusMessage());
+        assertTrue(res.isCommitted());
+    }
+
+    @Test public void testSendRedirect() {
+        res.build();
+        res.sendRedirect("/redirect");
+        assertEquals(302, res.getStatus());
+        assertNull(res.getStatusMessage());
+        assertTrue(res.isCommitted());
+        assertEquals("/redirect", res.getHeader("Location"));
+    }
+
+    @Test public void testHeaders() {
+        res.build();
+        assertTrue(res.getHeaderNames().isEmpty());
+        res.addDateHeader("date", 50000);
+        res.addIntHeader("number", 5);
+        res.addHeader("name", "value");
+        res.setDateHeader("adate", 100000);
+        res.setIntHeader("anumber", 10);
+        res.setHeader("aname", "something");
+
+        assertEquals(6, res.getHeaderNames().size());
+        assertEquals("Thu, 1 Jan 1970 00:00:50 GMT", res.getHeader("date"));
+        assertEquals("5", res.getHeader("number"));
+        assertEquals("value", res.getHeader("name"));
+        assertEquals("Thu, 1 Jan 1970 00:01:40 GMT", res.getHeader("adate"));
+        assertEquals("10", res.getHeader("anumber"));
+        assertEquals("something", res.getHeader("aname"));
+
+        assertTrue(res.containsHeader("name"));
+        assertFalse(res.containsHeader("foo"));
+
+        assertEquals(1, res.getHeaders("name").size());
+    }
+
+    @Test public void testGetWriter() {
+        res.build();
+        final PrintWriter writer = res.getWriter();
+        writer.write("body");
+        assertEquals("body", res.getOutputAsString());
+        assertEquals("body", new String(res.getOutput(), 
StandardCharsets.UTF_8));
+    }
+
+    @Test public void testGetOutputStream() throws IOException {
+        res.build();
+        final OutputStream out = res.getOutputStream();
+        out.write("body".getBytes(StandardCharsets.UTF_8));
+        assertEquals("body", res.getOutputAsString());
+        assertEquals("body", new String(res.getOutput(), 
StandardCharsets.UTF_8));
+    }
+
+    @Test public void testReset() {
+        res.build();
+        res.setStatus(201);
+        res.setContentLength(500);
+        res.reset();
+        assertEquals(200, res.getStatus());
+        assertEquals(-1L, res.getContentLength());
+    }
+
+    @Test public void testResetBuffer() {
+        res.build();
+        res.setStatus(201);
+        res.setContentLength(500);
+        res.resetBuffer();
+        assertEquals(201, res.getStatus());
+        assertEquals(500L, res.getContentLength());
+    }
+
+    @Test public void testBufferSize() {
+        res.build();
+        assertEquals(8192, res.getBufferSize());
+        res.setBufferSize(16384);
+        assertEquals(16384, res.getBufferSize());
+    }
+
+    @Test public void testFlushBuffer() {
+        res.build();
+        assertFalse(res.isCommitted());
+        res.flushBuffer();
+        assertTrue(res.isCommitted());
+    }
+
+    @Test public void testCookies() {
+        res.build();
+        assertNull(res.getCookies());
+        res.addCookie(new Cookie("name", "value"));
+        assertEquals(1, res.getCookies().length);
+        assertEquals("name", res.getCookies()[0].getName());
+        assertNotNull(res.getCookie("name"));
+    }
+
+    @Test public void testLocale() {
+        res.build();
+        assertEquals(Locale.US, res.getLocale());
+        res.setLocale(Locale.CANADA);
+        assertEquals(Locale.CANADA, res.getLocale());
+    }
+
+    @Test public void testUnsupportedMethods() {
+        res.build();
+        try {
+            res.encodeRedirectURL("/url");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            res.encodeRedirectUrl("/url");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            res.encodeURL("/url");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+        try {
+            res.encodeUrl("/url");
+            fail();
+        } catch ( final UnsupportedOperationException expected) {}
+    }
+}

Reply via email to