Merge pull request #4991 from JLLeitschuh/feat/JLL/early_ratpack_support

Java: Simple support for Ratpack HTTP Framework
This commit is contained in:
Anders Schack-Mulligen
2021-10-27 09:25:52 +02:00
committed by GitHub
51 changed files with 4637 additions and 2 deletions

View File

@@ -0,0 +1,33 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineExpectationsTest
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:frameworks:ratpack" }
override predicate isSource(DataFlow::Node n) {
n.asExpr().(MethodAccess).getMethod().hasName("taint")
or
n instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node n) {
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
}
}
class HasFlowTest extends InlineExpectationsTest {
HasFlowTest() { this = "HasFlowTest" }
override string getARelevantTag() { result = "hasTaintFlow" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasTaintFlow" and
exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) |
sink.getLocation() = location and
element = sink.toString() and
value = ""
)
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-core-2.12:${testdir}/../../../stubs/jackson-databind-2.12:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x

View File

@@ -0,0 +1,70 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import ratpack.core.handling.Context;
import ratpack.core.form.Form;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.function.Predicate;
public class CollectionPassingTest {
void sink(Object o) {}
String taint() {
return null;
}
void test_1(Context ctx) {
// Given
ctx
.getRequest()
.getBody()
.map(data -> ctx.parse(data, Form.form()))
.then(form -> {
// When
Map<String, Object> pojoMap = new HashMap<>();
merge(form.asMultimap().asMap(), pojoMap);
// Then
sink(pojoMap.get("value")); //$hasTaintFlow
pojoMap.forEach((key, value) -> {
sink(value); //$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(0)); //$hasTaintFlow
});
});
}
void test_2() {
// Given
Map<String, Collection<String>> taintedMap = new HashMap<>();
taintedMap.put("value", ImmutableList.of(taint()));
Map<String, Object> pojoMap = new HashMap<>();
// When
merge(taintedMap, pojoMap);
// Then
sink(pojoMap.get("value")); //$hasTaintFlow
pojoMap.forEach((key, value) -> {
sink(value); //$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(0)); //$hasTaintFlow
});
}
private static void merge(Map<String, Collection<String>> params, Map<String, Object> defaults) {
for(Map.Entry<String, Collection<String>> entry : params.entrySet()) {
String name = entry.getKey();
Collection<String> values = entry.getValue();
defaults.put(name, extractSingleValueIfPossible(values));
}
}
private static Object extractSingleValueIfPossible(Collection<String> values) {
return values.size() == 1 ? values.iterator().next() : ImmutableList.copyOf(values);
}
}

View File

@@ -0,0 +1,242 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.POJONode;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import ratpack.core.handling.Context;
import ratpack.core.http.TypedData;
import ratpack.core.form.Form;
import ratpack.core.form.UploadedFile;
import ratpack.core.parse.Parse;
import ratpack.exec.Promise;
import ratpack.func.Action;
import ratpack.func.Function;
import ratpack.func.MultiValueMap;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.function.Predicate;
import static ratpack.jackson.Jackson.jsonNode;
class IntegrationTest {
static class Pojo {
String value;
List<String> values;
String getValue() {
return value;
}
List<String> getValues() {
return values;
}
}
private final ObjectMapper objectMapper = new ObjectMapper();
void sink(Object o) {}
String taint() {
return null;
}
void test1(Context ctx) {
bindJson(ctx, Pojo.class)
.then(pojo ->{
sink(pojo); //$hasTaintFlow
sink(pojo.value); //$hasTaintFlow
sink(pojo.getValue()); //$hasTaintFlow
});
}
void test2(Context ctx) {
bindForm(ctx, Pojo.class, defaults -> defaults.put("another", "potato"))
.then(pojo ->{
sink(pojo); //$hasTaintFlow
sink(pojo.value); //$hasTaintFlow
sink(pojo.getValue()); //$hasTaintFlow
});
}
void test3() {
Object value = extractSingleValueIfPossible(ImmutableList.of("a", taint()));
sink(value); //$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(1)); //$hasTaintFlow
Map<String, Object> weirdMap = new HashMap<>();
weirdMap.put("a", value);
weirdMap.forEach((key, mapValue) -> {
sink(mapValue); //$hasTaintFlow
List<Object> values2 = (List<Object>) mapValue;
sink(values2.get(0)); //$hasTaintFlow
});
}
void test4(Context ctx) {
parseToForm(ctx, Pojo.class)
.map(pojoForm -> {
Map<String, Object> mergedParams = new HashMap<>();
filterAndMerge(pojoForm, mergedParams, name -> false);
return mergedParams;
}).then(pojoMap -> {
sink(pojoMap.keySet().iterator().next()); //$hasTaintFlow
sink(pojoMap.get("value")); //$hasTaintFlow
pojoMap.forEach((key, value) -> {
sink(key); //$hasTaintFlow
sink(value); //$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(0)); //$hasTaintFlow
});
});
}
void test5(Context ctx) {
parseToForm(ctx, Pojo.class)
.map(pojoForm -> {
Map<String, Object> mergedParams = new HashMap<>();
filterAndMerge_2(pojoForm, mergedParams, name -> false);
return mergedParams;
}).then(pojoMap -> {
sink(pojoMap.keySet().iterator().next()); //TODO:$hasTaintFlow
sink(pojoMap.get("value")); //TODO:$hasTaintFlow
pojoMap.forEach((key, value) -> {
sink(key); //TODO:$hasTaintFlow
sink(value); //TODO:$hasTaintFlow
List<Object> values = (List<Object>) value;
sink(values.get(0)); //TODO:$hasTaintFlow
});
});
}
void test6(Context ctx) {
bindQuery(ctx, Pojo.class)
.then(pojo -> {
sink(pojo.getValue()); //$hasTaintFlow
sink(pojo.getValues()); //$hasTaintFlow
});
}
public <T> Promise<T> bindQuery(Context ctx, Class<T> type) {
return bindQuery(ctx, type, Action.noop());
}
public <T> Promise<T> bindQuery(Context ctx, Class<T> type, Action<? super ImmutableMap.Builder<String, Object>> defaults) {
return Promise.sync(() ->
bind(ctx, toObjectNode(ctx.getRequest().getQueryParams(), defaults, name -> false), type)
);
}
private <T> Promise<T> bindJson(Context ctx, Class<T> type) {
return ctx.getRequest().getBody()
.map(data -> {
String dataText = data.getText();
try {
return ctx.parse(data, jsonNode(objectMapper));
} catch (Exception e) {
String msg = "Unable to parse json data while binding type " + type.getCanonicalName() + " [jsonData: " + dataText + "]";
throw new RuntimeException(msg, e);
}
})
.map(json ->
bind(ctx, json, type)
);
}
private <T> T bind(Context ctx, JsonNode input, Class<T> type) {
T value;
try {
value = objectMapper.convertValue(input, type);
} catch (Exception e) {
throw new RuntimeException("Failed to convert input to " + type.getName(), e);
}
return value;
}
private static Promise<Form> parseToForm(Context ctx, Class<?> type) {
return ctx.getRequest().getBody()
.map(data -> {
try {
return ctx.parse(data, Form.form());
} catch (Exception e) {
String msg = "Unable to parse form data while binding type " + type.getCanonicalName() + " [formData: " + data.getText() + "]";
throw new RuntimeException(msg, e);
}
});
}
private <T> Promise<T> bindForm(Context ctx, Class<T> type, Action<? super ImmutableMap.Builder<String, Object>> defaults) {
return parseToForm(ctx, type)
.map(form -> {
ObjectNode input = toObjectNode(form, defaults, s -> false);
Map<String, List<UploadedFile>> filesMap = form.files().getAll();
filesMap.forEach((name, files) -> {
ArrayNode array = input.putArray(name);
files.forEach(f -> array.add(new POJONode(new UploadedFileWrapper(f))));
});
return bind(ctx, input, type);
});
}
private ObjectNode toObjectNode(MultiValueMap<String, String> params, Action<? super ImmutableMap.Builder<String, Object>> defaults, Predicate<String> paramFilter) throws Exception {
Map<String, Object> mergedParams = new HashMap<>(defaults.with(ImmutableMap.builder()).build());
filterAndMerge(params, mergedParams, paramFilter);
return objectMapper.valueToTree(mergedParams);
}
private static void filterAndMerge(MultiValueMap<String, String> params, Map<String, Object> defaults, Predicate<String> filter) {
for(Map.Entry<String, Collection<String>> entry : params.asMultimap().asMap().entrySet()) {
String name = entry.getKey();
Collection<String> values = entry.getValue();
if (!isEmptyAndHasDefault(name, values, defaults) && !filter.test(name)) {
defaults.put(name, extractSingleValueIfPossible(values));
}
}
}
private static void filterAndMerge_2(MultiValueMap<String, String> params, Map<String, Object> defaults, Predicate<String> filter) {
params.asMultimap().asMap().forEach((name, values) -> {
if (!isEmptyAndHasDefault(name, values, defaults) && !filter.test(name)) {
defaults.put(name, extractSingleValueIfPossible(values));
}
});
}
private static boolean isEmptyAndHasDefault(String name, Collection<String> values, Map<String, Object> defaults) {
// STUB - This is to make the compiler happy
return false;
}
private static Object extractSingleValueIfPossible(Collection<String> values) {
return values.size() == 1 ? values.iterator().next() : ImmutableList.copyOf(values);
}
private static class UploadedFileWrapper implements JsonSerializable {
private final UploadedFile file;
private UploadedFileWrapper(UploadedFile file) {
this.file = file;
}
@Override
public void serialize(Object gen, Object serializers) throws IOException {
// empty
}
@Override
public void serializeWithType(Object gen, Object serializers, Object typeSer) throws IOException {
// empty
}
}
}

View File

@@ -0,0 +1,320 @@
import ratpack.core.handling.Context;
import ratpack.core.http.TypedData;
import ratpack.core.form.Form;
import ratpack.core.form.UploadedFile;
import ratpack.core.parse.Parse;
import ratpack.exec.Promise;
import ratpack.func.Action;
import ratpack.func.Function;
import java.io.OutputStream;
class Resource {
void sink(Object o) {}
String taint() {
return null;
}
void test1(Context ctx) {
sink(ctx.getRequest().getContentLength()); //$hasTaintFlow
sink(ctx.getRequest().getCookies()); //$hasTaintFlow
sink(ctx.getRequest().oneCookie("Magic-Cookie")); //$hasTaintFlow
sink(ctx.getRequest().getHeaders()); //$hasTaintFlow
sink(ctx.getRequest().getHeaders().get("questionable_header")); //$hasTaintFlow
sink(ctx.getRequest().getHeaders().getAll("questionable_header")); //$hasTaintFlow
sink(ctx.getRequest().getHeaders().getNames()); //$hasTaintFlow
sink(ctx.getRequest().getHeaders().asMultiValueMap()); //$hasTaintFlow
sink(ctx.getRequest().getHeaders().asMultiValueMap().get("questionable_header")); //$hasTaintFlow
sink(ctx.getRequest().getPath()); //$hasTaintFlow
sink(ctx.getRequest().getQuery()); //$hasTaintFlow
sink(ctx.getRequest().getQueryParams()); //$hasTaintFlow
sink(ctx.getRequest().getQueryParams().get("questionable_parameter")); //$hasTaintFlow
sink(ctx.getRequest().getRawUri()); //$hasTaintFlow
sink(ctx.getRequest().getUri()); //$hasTaintFlow
}
void test2(Context ctx, OutputStream os) {
ctx.getRequest().getBody().then(td -> {
sink(td); //$hasTaintFlow
sink(td.getText()); //$hasTaintFlow
sink(td.getBuffer()); //$hasTaintFlow
sink(td.getBytes()); //$hasTaintFlow
sink(td.getContentType()); //$hasTaintFlow
sink(td.getInputStream()); //$hasTaintFlow
sink(os);
td.writeTo(os);
sink(os); //$hasTaintFlow
if (td instanceof UploadedFile) {
UploadedFile uf = (UploadedFile) td;
sink(uf.getFileName()); //$hasTaintFlow
}
});
}
void test3(Context ctx) {
ctx.getRequest().getBody().map(TypedData::getText).then(s -> {
sink(s); //$hasTaintFlow
});
ctx.getRequest().getBody().map(b -> {
sink(b); //$hasTaintFlow
sink(b.getText()); //$hasTaintFlow
return b.getText();
}).then(t -> {
sink(t); //$hasTaintFlow
});
ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow
ctx
.getRequest()
.getBody()
.map(TypedData::getText)
.next(this::sink) //$hasTaintFlow
.then(this::sink); //$hasTaintFlow
}
void test4() {
String tainted = taint();
Promise.value(tainted);
Promise
.value(tainted)
.then(this::sink); //$hasTaintFlow
Promise
.value(tainted)
.map(a -> a)
.then(this::sink); //$hasTaintFlow
}
void test5(Context ctx) {
ctx
.getRequest()
.getBody()
.map(data -> {
Form form = ctx.parse(data, Form.form());
sink(form); //$hasTaintFlow
return form;
})
.then(form -> {
sink(form.file("questionable_file")); //$hasTaintFlow
sink(form.file("questionable_file").getFileName()); //$hasTaintFlow
sink(form.files("questionable_files")); //$hasTaintFlow
sink(form.files()); //$hasTaintFlow
sink(form.get("questionable_parameter")); //$hasTaintFlow
sink(form.getAll().get("questionable_parameter").get(0)); //$hasTaintFlow
sink(form.getAll("questionable_parameter").get(0)); //$hasTaintFlow
sink(form.asMultimap().get("questionable_parameter")); //$hasTaintFlow
sink(form.asMultimap().asMap()); //$hasTaintFlow
form.asMultimap().asMap().forEach((name, values) -> {
sink(name); //$hasTaintFlow
sink(values); //$hasTaintFlow
});
});
}
void test6(Context ctx) {
ctx
.parse(Parse.of(Form.class))
.then(form -> {
sink(form); //$hasTaintFlow
});
ctx
.parse(Form.class)
.then(form -> {
sink(form); //$hasTaintFlow
});
ctx
.parse(Form.class, "Some Object")
.then(form -> {
sink(form); //$hasTaintFlow
});
}
void test7() {
String tainted = taint();
Promise
.flatten(() -> Promise.value(tainted))
.next(value -> {
sink(value); //$hasTaintFlow
})
.onError(Action.noop())
.next(value -> {
sink(value); //$hasTaintFlow
})
.cache()
.next(value -> {
sink(value); //$hasTaintFlow
})
.fork()
.next(value -> {
sink(value); //$hasTaintFlow
})
.route(value -> {
sink(value); //$hasTaintFlow
return false;
}, value -> {
sink(value); //$hasTaintFlow
})
.next(value -> {
sink(value); //$hasTaintFlow
})
.cacheIf(value -> {
sink(value); //$hasTaintFlow
return true;
})
.next(value -> {
sink(value); //$hasTaintFlow
})
.onError(RuntimeException.class, Action.noop())
.next(value -> {
sink(value); //$hasTaintFlow
})
.then(value -> {
sink(value); //$hasTaintFlow
});
}
void test8() {
String tainted = taint();
Promise
.sync(() -> tainted)
.mapError(RuntimeException.class, exception -> {
sink(exception); // no taint
return "potato";
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.mapError(RuntimeException.class, exception -> {
return taint();
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value(tainted)
.flatMapError(RuntimeException.class, exception -> {
sink(exception); // no taint
return Promise.value("potato");
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.flatMapError(RuntimeException.class, exception -> {
return Promise.value(taint());
})
.then(value -> {
sink(value); //$hasTaintFlow
});
}
void test9() {
String tainted = taint();
Promise
.value(tainted)
.map(Resource::identity)
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.map(Resource::identity)
.then(value -> {
sink(value); // no taints flow
});
Promise
.value(tainted)
.flatMap(v -> Promise.value(v))
.then(value -> {
sink(value); //$hasTaintFlow
});
}
public static String identity(String input) {
return input;
}
void test10() {
String tainted = taint();
Promise
.value(tainted)
.apply(Resource::promiseIdentity)
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.apply(Resource::promiseIdentity)
.then(value -> {
sink(value); // no taints flow
});
}
public static Promise<String> promiseIdentity(Promise<String> input) {
return input.map(i -> i);
}
void test11() {
String tainted = taint();
Promise
.value(tainted)
.map(a -> a)
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.value("potato")
.map(a -> a)
.then(value -> {
sink(value); // no taints flow
});
}
void test12() {
String tainted = taint();
Promise
.sync(() -> tainted)
.mapIf(v -> {
sink(v); //$hasTaintFlow
return true;
}, v -> {
sink(v); //$hasTaintFlow
return v;
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.sync(() -> tainted)
.mapIf(v -> {
sink(v); //$hasTaintFlow
return true;
}, vTrue -> {
sink(vTrue); //$hasTaintFlow
return vTrue;
}, vFalse -> {
sink(vFalse); //$hasTaintFlow
return vFalse;
})
.then(value -> {
sink(value); //$hasTaintFlow
});
Promise
.sync(() -> tainted)
.mapIf(v -> {
sink(v); //$hasTaintFlow
return true;
}, vTrue -> {
sink(vTrue); //$hasTaintFlow
return "potato";
}, vFalse -> {
sink(vFalse); //$hasTaintFlow
return "potato";
})
.then(value -> {
sink(value); // no tainted flow
});
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2006 The Guava Authors
*
* Licensed 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 com.google.common.reflect;
import java.io.Serializable;
public abstract class TypeToken<T> implements Serializable {
}

View File

@@ -9,7 +9,9 @@ package com.fasterxml.jackson.core;
import java.util.Iterator;
public interface TreeNode {
JsonParser.NumberType numberType();
default JsonParser.NumberType numberType() {
return null;
}
int size();
@@ -35,6 +37,8 @@ public interface TreeNode {
TreeNode at(String jsonPointerExpression) throws IllegalArgumentException;
JsonParser traverse();
default JsonParser traverse() {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package com.fasterxml.jackson.databind;
import java.io.IOException;
// This interface does not actually have these types.. This is a significantly oversimplified stub.
public interface JsonSerializable {
public void serialize(Object gen, Object serializers) throws IOException;
public void serializeWithType(Object gen, Object serializers, Object typeSer) throws IOException;
}

View File

@@ -0,0 +1,10 @@
package com.fasterxml.jackson.databind.node;
import com.fasterxml.jackson.databind.JsonNode;
public abstract class ArrayNode extends ContainerNode<ArrayNode> {
public ArrayNode add(JsonNode value) {
return null;
}
}

View File

@@ -0,0 +1,7 @@
package com.fasterxml.jackson.databind.node;
import com.fasterxml.jackson.databind.JsonNode;
public abstract class BaseJsonNode extends JsonNode {
}

View File

@@ -0,0 +1,5 @@
package com.fasterxml.jackson.databind.node;
public abstract class ContainerNode<T extends ContainerNode<T>> extends BaseJsonNode {
}

View File

@@ -0,0 +1,9 @@
package com.fasterxml.jackson.databind.node;
public abstract class ObjectNode extends ContainerNode<ObjectNode> {
public ArrayNode putArray(String propertyName)
{
return null;
}
}

View File

@@ -0,0 +1,76 @@
package com.fasterxml.jackson.databind.node;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
public class POJONode extends ValueNode {
protected final Object _value;
public POJONode(Object v) {
_value = v;
}
@Override
public boolean equals(Object o) {
return false;
}
public String toString() {
return null;
}
@Override
public <T extends JsonNode> T deepCopy() {
return null;
}
@Override
public JsonNode get(int index) {
return null;
}
@Override
public JsonNode path(String fieldName) {
return null;
}
@Override
public JsonNode path(int index) {
return null;
}
@Override
public String asText() {
return null;
}
@Override
public JsonNode findValue(String fieldName) {
return null;
}
@Override
public JsonNode findPath(String fieldName) {
return null;
}
@Override
public JsonNode findParent(String fieldName) {
return null;
}
@Override
public List<JsonNode> findValues(String fieldName, List<JsonNode> foundSoFar) {
return null;
}
@Override
public List<String> findValuesAsText(String fieldName, List<String> foundSoFar) {
return null;
}
@Override
public List<JsonNode> findParents(String fieldName, List<JsonNode> foundSoFar) {
return null;
}
}

View File

@@ -0,0 +1,5 @@
package com.fasterxml.jackson.databind.node;
public abstract class ValueNode extends BaseJsonNode {
}

View File

@@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.buffer;
public abstract class ByteBuf implements Comparable<ByteBuf> {
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
/**
* An interface defining an
* <a href="https://en.wikipedia.org/wiki/HTTP_cookie">HTTP cookie</a>.
*/
public interface Cookie extends Comparable<Cookie> {
/**
* Constant for undefined MaxAge attribute value.
*/
long UNDEFINED_MAX_AGE = Long.MIN_VALUE;
/**
* Returns the name of this {@link Cookie}.
*
* @return The name of this {@link Cookie}
*/
String name();
/**
* Returns the value of this {@link Cookie}.
*
* @return The value of this {@link Cookie}
*/
String value();
/**
* Sets the value of this {@link Cookie}.
*
* @param value The value to set
*/
void setValue(String value);
/**
* Returns true if the raw value of this {@link Cookie},
* was wrapped with double quotes in original Set-Cookie header.
*
* @return If the value of this {@link Cookie} is to be wrapped
*/
boolean wrap();
/**
* Sets true if the value of this {@link Cookie}
* is to be wrapped with double quotes.
*
* @param wrap true if wrap
*/
void setWrap(boolean wrap);
/**
* Returns the domain of this {@link Cookie}.
*
* @return The domain of this {@link Cookie}
*/
String domain();
/**
* Sets the domain of this {@link Cookie}.
*
* @param domain The domain to use
*/
void setDomain(String domain);
/**
* Returns the path of this {@link Cookie}.
*
* @return The {@link Cookie}'s path
*/
String path();
/**
* Sets the path of this {@link Cookie}.
*
* @param path The path to use for this {@link Cookie}
*/
void setPath(String path);
/**
* Returns the maximum age of this {@link Cookie} in seconds or {@link Cookie#UNDEFINED_MAX_AGE} if unspecified
*
* @return The maximum age of this {@link Cookie}
*/
long maxAge();
/**
* Sets the maximum age of this {@link Cookie} in seconds.
* If an age of {@code 0} is specified, this {@link Cookie} will be
* automatically removed by browser because it will expire immediately.
* If {@link Cookie#UNDEFINED_MAX_AGE} is specified, this {@link Cookie} will be removed when the
* browser is closed.
*
* @param maxAge The maximum age of this {@link Cookie} in seconds
*/
void setMaxAge(long maxAge);
/**
* Checks to see if this {@link Cookie} is secure
*
* @return True if this {@link Cookie} is secure, otherwise false
*/
boolean isSecure();
/**
* Sets the security getStatus of this {@link Cookie}
*
* @param secure True if this {@link Cookie} is to be secure, otherwise false
*/
void setSecure(boolean secure);
/**
* Checks to see if this {@link Cookie} can only be accessed via HTTP.
* If this returns true, the {@link Cookie} cannot be accessed through
* client side script - But only if the browser supports it.
* For more information, please look <a href="https://owasp.org/www-community/HttpOnly">here</a>
*
* @return True if this {@link Cookie} is HTTP-only or false if it isn't
*/
boolean isHttpOnly();
/**
* Determines if this {@link Cookie} is HTTP only.
* If set to true, this {@link Cookie} cannot be accessed by a client
* side script. However, this works only if the browser supports it.
* For for information, please look
* <a href="https://owasp.org/www-community/HttpOnly">here</a>.
*
* @param httpOnly True if the {@link Cookie} is HTTP only, otherwise false.
*/
void setHttpOnly(boolean httpOnly);
}

View File

@@ -0,0 +1,15 @@
/*
* Licensed 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.
*
*/

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.form;
import ratpack.core.handling.Context;
import ratpack.core.parse.Parse;
import ratpack.func.Nullable;
import ratpack.func.MultiValueMap;
import java.util.List;
/**
* An uploaded form.
* <p>
* The form is modelled as a {@link MultiValueMap}, with extra methods for dealing with file uploads.
* That is, uploaded files are not visible via the methods provided by {@link MultiValueMap}.
* <p>
* All instances of this type are <b>immutable</b>.
* Calling any mutative method of {@link MultiValueMap} will result in an {@link UnsupportedOperationException}.
* <h3>Example usage:</h3>
* <pre class="java">{@code
* import ratpack.core.handling.Handler;
* import ratpack.core.handling.Context;
* import ratpack.core.form.Form;
* import ratpack.core.form.UploadedFile;
*
* import java.util.List;
*
* public class Example {
* public static class FormHandler implements Handler {
* public void handle(Context context) throws Exception {
* context.parse(Form.class).then(form -> {
* UploadedFile file = form.file("someFile.txt");
* String param = form.get("param");
* List<String> multi = form.getAll("multi");
* context.render("form uploaded!");
* });
* }
* }
* }
* }</pre>
*
* <p>
* To include the query parameters from the request in the parsed form, use {@link Form#form(boolean)}.
* This can be useful if you want to support both {@code GET} and {@code PUT} submission with a single handler.
*/
public interface Form extends MultiValueMap<String, String> {
/**
* Return the first uploaded file with the given name.
*
* @param name The name of the uploaded file in the form
* @return The uploaded file, or {@code null} if no file was uploaded by that name
*/
@Nullable
UploadedFile file(String name);
/**
* Return all of the uploaded files with the given name.
*
* @param name The name of the uploaded files in the form
* @return The uploaded files, or an empty list if no files were uploaded by that name
*/
List<UploadedFile> files(String name);
/**
* Returns all of the uploaded files.
*
* @return all of the uploaded files.
*/
MultiValueMap<String, UploadedFile> files();
/**
* Creates a {@link Context#parse parseable object} to parse a request body into a {@link Form}.
* <p>
* Default options will be used (no query parameters included).
*
* @return a parse object
*/
static Parse<Form, FormParseOpts> form() {
return null;
}
/**
* Creates a {@link Context#parse parseable object} to parse a request body into a {@link Form}.
*
* @param includeQueryParams whether to include the query parameters from the request in the parsed form
* @return a parse object
*/
static Parse<Form, FormParseOpts> form(boolean includeQueryParams) {
return null;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed 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 ratpack.core.form;
/**
* Options for parsing a {@link Form}.
*/
public interface FormParseOpts {
/**
* Whether to include the query parameters from the request in the parsed form.
*
* @return whether to include the query parameters from the request in the parsed form
*/
boolean isIncludeQueryParams();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.form;
import ratpack.core.http.TypedData;
import ratpack.func.Nullable;
/**
* A file that was uploaded via a form.
*
* @see Form
*/
public interface UploadedFile extends TypedData {
/**
* The name given for the file.
*
* @return The name given for the file, or {@code null} if no name was provided.
*/
@Nullable
String getFileName();
}

View File

@@ -0,0 +1,472 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.handling;
import com.google.common.reflect.TypeToken;
import ratpack.core.http.*;
import ratpack.core.parse.Parse;
import ratpack.core.render.*;
import ratpack.exec.api.NonBlocking;
import ratpack.exec.registry.NotInRegistryException;
import ratpack.exec.registry.Registry;
import ratpack.exec.Promise;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
/**
* The context of an individual {@link Handler} invocation.
* <p>
* It provides:
* <ul>
* <li>Access the HTTP {@link #getRequest() request} and {@link #getResponse() response}</li>
* <li>Delegation (via the {@link #next} and {@link #insert} family of methods)</li>
* <li>Access to <i>contextual objects</i> (see below)</li>
* <li>Convenience for common handler operations</li>
* </ul>
*
* <h3>Contextual objects</h3>
* <p>
* A context is also a {@link Registry} of objects.
* Arbitrary objects can be "pushed" into the context for use by <i>downstream</i> handlers.
* <p>
* There are some significant contextual objects that drive key infrastructure.
* For example, error handling is based on informing the contextual {@link ServerErrorHandler} of exceptions.
* The error handling strategy for an application can be changed by pushing a new implementation of this interface into the context that is used downstream.
* <p>
* See {@link #insert(Handler...)} for more on how to do this.
* <h4>Default contextual objects</h4>
* <p>There is also a set of default objects that are made available via the Ratpack infrastructure:
* <ul>
* <li>A {@link FileSystemBinding} that is the application {@link ServerConfig#getBaseDir()}</li>
* <li>A {@link MimeTypes} implementation</li>
* <li>A {@link ServerErrorHandler}</li>
* <li>A {@link ClientErrorHandler}</li>
* <li>A {@link PublicAddress}</li>
* <li>A {@link Redirector}</li>
* </ul>
*/
public interface Context extends Registry {
/**
* A type token for this type.
*
* @since 1.1
*/
TypeToken<Context> TYPE = null;
/**
* Returns this.
*
* @return this.
*/
Context getContext();
/**
* The HTTP request.
*
* @return The HTTP request.
*/
Request getRequest();
/**
* The HTTP response.
*
* @return The HTTP response.
*/
Response getResponse();
/**
* Delegate handling to the next handler in line.
* <p>
* The request and response of this object should not be accessed after this method is called.
*/
@NonBlocking
void next();
/**
* Invokes the next handler, after adding the given registry.
* <p>
* The given registry is appended to the existing.
* This means that it can shadow objects previously available.
* <pre class="java">{@code
* import ratpack.exec.registry.Registry;
* import ratpack.test.embed.EmbeddedApp;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static void main(String... args) throws Exception {
* EmbeddedApp.fromHandlers(chain -> chain
* .all(ctx -> ctx.next(Registry.single("foo")))
* .all(ctx -> ctx.render(ctx.get(String.class)))
* ).test(httpClient -> {
* assertEquals("foo", httpClient.getText());
* });
* }
* }
* }</pre>
*
* @param registry The registry to make available for subsequent handlers.
*/
@NonBlocking
void next(Registry registry);
/**
* Inserts some handlers into the pipeline, then delegates to the first.
* <p>
* The request and response of this object should not be accessed after this method is called.
*
* @param handlers The handlers to insert.
*/
@NonBlocking
void insert(Handler... handlers);
/**
* Inserts some handlers into the pipeline to execute with the given registry, then delegates to the first.
* <p>
* The given registry is only applicable to the inserted handlers.
* <p>
* Almost always, the registry should be a super set of the current registry.
*
* @param handlers The handlers to insert
* @param registry The registry for the inserted handlers
*/
@NonBlocking
void insert(Registry registry, Handler... handlers);
/**
* Handles any error thrown during request handling.
* <p>
* Uncaught exceptions that are thrown any time during request handling will end up here.
* <p>
* Forwards the exception to the {@link ServerErrorHandler} within the current registry.
* Add an implementation of this interface to the registry to handle errors.
* The default implementation is not suitable for production usage.
* <p>
* If the exception is-a {@link ClientErrorException},
* the {@link #clientError(int)} method will be called with the exception's status code
* instead of being forward to the server error handler.
*
* @param throwable The exception that occurred
*/
@NonBlocking
void error(Throwable throwable);
/**
* Forwards the error to the {@link ClientErrorHandler} in this service.
*
* The default configuration of Ratpack includes a {@link ClientErrorHandler} in all contexts.
* A {@link NotInRegistryException} will only be thrown if a very custom service setup is being used.
*
* @param statusCode The 4xx range status code that indicates the error type
* @throws NotInRegistryException if no {@link ClientErrorHandler} can be found in the service
*/
@NonBlocking
void clientError(int statusCode) throws NotInRegistryException;
/**
* Render the given object, using the rendering framework.
* <p>
* The first {@link Renderer}, that is able to render the given object will be delegated to.
* If the given argument is {@code null}, this method will have the same effect as {@link #clientError(int) clientError(404)}.
* <p>
* If no renderer can be found for the given type, a {@link NoSuchRendererException} will be given to {@link #error(Throwable)}.
* <p>
* If a renderer throws an exception during its execution it will be wrapped in a {@link RendererException} and given to {@link #error(Throwable)}.
* <p>
* Ratpack has built in support for rendering the following types:
* <ul>
* <li>{@link Path}</li>
* <li>{@link CharSequence}</li>
* <li>{@link JsonRender} (Typically created via {@link Jackson#json(Object)})</li>
* <li>{@link Promise} (renders the promised value, using this {@code render()} method)</li>
* <li>{@link Publisher} (converts the publisher to a promise using {@link Streams#toPromise(Publisher)} and renders it)</li>
* <li>{@link Renderable} (Delegates to the {@link Renderable#render(Context)} method of the object)</li>
* </ul>
* <p>
* See {@link Renderer} for more on how to contribute to the rendering framework.
* <p>
* The object-to-render will be decorated by all registered {@link RenderableDecorator} whose type is exactly equal to the type of the object-to-render, before being passed to the selected renderer.
*
* @param object The object-to-render
* @throws NoSuchRendererException if no suitable renderer can be found
*/
@NonBlocking
void render(Object object) throws NoSuchRendererException;
/**
* Returns the request header with the specified name.
* <p>
* If there is more than value for the specified header, the first value is returned.
* <p>
* Shorthand for {@code getRequest().getHeaders().get(String)}.
*
* @param name the case insensitive name of the header to get retrieve the first value of
* @return the header value or {@code null} if there is no such header
* @since 1.4
*/
default Optional<String> header(CharSequence name) {
return Optional.ofNullable(getRequest().getHeaders().get(name));
}
/**
* Sets a response header.
* <p>
* Any previously set values for the header will be removed.
* <p>
* Shorthand for {@code getResponse().getHeaders().set(CharSequence, Iterable)}.
*
* @param name the name of the header to set
* @param values the header values
* @return {@code this}
* @since 1.4
* @see MutableHeaders#set(CharSequence, Iterable)
*/
default Context header(CharSequence name, Object... values) {
getResponse().getHeaders().set(name, Arrays.asList(values));
return this;
}
/**
* Sends a temporary redirect response (i.e. 302) to the client using the specified redirect location.
*
* @param to the location to redirect to
* @see #redirect(int, Object)
* @since 1.3
*/
void redirect(Object to);
/**
* Sends a redirect response to the given location, and with the given status code.
* <p>
* This method retrieves the {@link Redirector} from the registry, and forwards the given arguments along with {@code this} context.
*
* @param code The status code of the redirect
* @param to the redirect location URL
* @see Redirector
* @since 1.3
*/
void redirect(int code, Object to);
/**
* Convenience method for handling last-modified based HTTP caching.
* <p>
* The given date is the "last modified" value of the response.
* If the client sent an "If-Modified-Since" header that is of equal or greater value than date,
* a 304 will be returned to the client.
* Otherwise, the given runnable will be executed (it should send a response)
* and the "Last-Modified" header will be set by this method.
*
* @param lastModified the effective last modified date of the response
* @param serve the response sending action if the response needs to be sent
*/
@NonBlocking
default void lastModified(Date lastModified, Runnable serve) {
lastModified(lastModified.toInstant(), serve);
}
/**
* Convenience method for handling last-modified based HTTP caching.
* <p>
* The given date is the "last modified" value of the response.
* If the client sent an "If-Modified-Since" header that is of equal or greater value than date,
* a 304 will be returned to the client.
* Otherwise, the given runnable will be executed (it should send a response)
* and the "Last-Modified" header will be set by this method.
*
* @param lastModified the effective last modified date of the response
* @param serve the response sending action if the response needs to be sent
* @since 1.4
*/
@NonBlocking
void lastModified(Instant lastModified, Runnable serve);
/**
* Parse the request into the given type, using no options (or more specifically an instance of {@link NullParseOpts} as the options).
* <p>
* The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant
* <pre class="java">{@code
* import ratpack.core.handling.Handler;
* import ratpack.core.handling.Context;
* import ratpack.core.form.Form;
*
* public class FormHandler implements Handler {
* public void handle(Context context) {
* context.parse(Form.class).then(form -> context.render(form.get("someFormParam")));
* }
* }
* }</pre>
* <p>
* That is, it is a convenient form of {@code parse(Parse.of(T))}.
*
* @param type the type to parse to
* @param <T> the type to parse to
* @return a promise for the parsed object
*/
<T> Promise<T> parse(Class<T> type);
/**
* Parse the request into the given type, using no options (or more specifically an instance of {@link NullParseOpts} as the options).
* <p>
* The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant&hellip;
* <pre class="java">{@code
* import ratpack.core.handling.Handler;
* import ratpack.core.handling.Context;
* import ratpack.core.form.Form;
* import com.google.common.reflect.TypeToken;
*
* public class FormHandler implements Handler {
* public void handle(Context context) {
* context.parse(new TypeToken<Form>() {}).then(form -> context.render(form.get("someFormParam")));
* }
* }
* }</pre>
* <p>
* That is, it is a convenient form of {@code parse(Parse.of(T))}.
*
* @param type the type to parse to
* @param <T> the type to parse to
* @return a promise for the parsed object
*/
<T> Promise<T> parse(TypeToken<T> type);
/**
* Constructs a {@link Parse} from the given args and delegates to {@link #parse(Parse)}.
*
* @param type The type to parse to
* @param options The parse options
* @param <T> The type to parse to
* @param <O> The type of the parse opts
* @return a promise for the parsed object
*/
<T, O> Promise<T> parse(Class<T> type, O options);
/**
* Constructs a {@link Parse} from the given args and delegates to {@link #parse(Parse)}.
*
* @param type The type to parse to
* @param options The parse options
* @param <T> The type to parse to
* @param <O> The type of the parse opts
* @return a promise for the parsed object
*/
<T, O> Promise<T> parse(TypeToken<T> type, O options);
/**
* Parses the request body into an object.
* <p>
* How to parse the request is determined by the given {@link Parse} object.
*
* <h3>Parser Resolution</h3>
* <p>
* Parser resolution happens as follows:
* <ol>
* <li>All {@link Parser parsers} are retrieved from the context registry (i.e. {@link #getAll(Class) getAll(Parser.class)});</li>
* <li>Found parsers are checked (in order returned by {@code getAll()}) for compatibility with the options type;</li>
* <li>If a parser is found that is compatible, its {@link Parser#parse(Context, TypedData, Parse)} method is called;</li>
* <li>If the parser returns {@code null} the next parser will be tried, if it returns a value it will be returned by this method;</li>
* <li>If no compatible parser could be found, a {@link NoSuchParserException} will be thrown.</li>
* </ol>
*
* <h3>Parser Compatibility</h3>
* <p>
* A parser is compatible if all of the following hold true:
* <ul>
* <li>The opts of the given {@code parse} object is an {@code instanceof} its {@link Parser#getOptsType()}, or the opts are {@code null}.</li>
* <li>The {@link Parser#parse(Context, TypedData, Parse)} method returns a non null value.</li>
* </ul>
*
* <h3>Core Parsers</h3>
* <p>
* Ratpack core provides parsers for {@link Form}, and JSON (see {@link Jackson}).
*
* <h3>Example Usage</h3>
* <pre class="java">{@code
* import ratpack.core.handling.Handler;
* import ratpack.core.handling.Context;
* import ratpack.core.form.Form;
* import ratpack.core.parse.NullParseOpts;
*
* public class FormHandler implements Handler {
* public void handle(Context context) {
* context.parse(Form.class).then(form -> context.render(form.get("someFormParam")));
* }
* }
* }</pre>
*
* @param parse The specification of how to parse the request
* @param <T> The type of object the request is parsed into
* @param <O> the type of the parse options object
* @return a promise for the parsed object
* @see #parse(Class)
* @see #parse(Class, Object)
* @see Parser
*/
<T, O> Promise<T> parse(Parse<T, O> parse);
/**
* Parses the provided request body into an object.
* <p>
* This variant can be used when a reference to the request body has already been obtained.
* For example, this can be used during the implementation of a {@link Parser} that needs to delegate to another parser.
* <p>
* From within a handler, it is more common to use {@link #parse(Parse)} or similar.
*
* @param body The request body
* @param parse The specification of how to parse the request
* @param <T> The type of object the request is parsed into
* @param <O> The type of the parse options object
* @return a promise for the parsed object
* @see #parse(Parse)
* @throws Exception any thrown by the parser
*/
<T, O> T parse(TypedData body, Parse<T, O> parse) throws Exception;
/**
* Gets the file relative to the contextual {@link FileSystemBinding}.
* <p>
* Shorthand for {@code get(FileSystemBinding.class).file(path)}.
* <p>
* The default configuration of Ratpack includes a {@link FileSystemBinding} in all contexts.
* A {@link NotInRegistryException} will only be thrown if a very custom service setup is being used.
*
* @param path The path to pass to the {@link FileSystemBinding#file(String)} method.
* @return The file relative to the contextual {@link FileSystemBinding}
* @throws NotInRegistryException if there is no {@link FileSystemBinding} in the current service
*/
Path file(String path) throws NotInRegistryException;
/**
* Issues a 404 client error.
* <p>
* This method is literally a shorthand for {@link #clientError(int) clientError(404)}.
* <p>
* This is a terminal handler operation.
*
* @since 1.1
*/
default void notFound() {
clientError(404);
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.handling;
import ratpack.exec.registry.Registry;
/**
* A handler participates in the processing of a request/response pair, operating on a {@link Context}.
* <p>
* Handlers are the key component of Ratpack applications.
* A handler either generate a response, or delegate to another handler in some way.
* </p>
* <h3>Non blocking/Asynchronous</h3>
* <p>
* Handlers are expected to be asynchronous.
* That is, there is no expectation that the handler is "finished" when its {@link #handle(Context)} method returns.
* This facilitates the use of non blocking IO without needing to enter some kind of special mode.
* An implication is that handlers <b>must</b> ensure that they either send a response or delegate to another handler.
* </p>
* <h3>Handler pipeline</h3>
* <p>
* Handlers are always part of a pipeline structure.
* The {@link Context} that the handler operates on provides the {@link Context#next()} method that passes control to the next handler in the pipeline.
* The last handler in the pipeline is always that generates a {@code 404} client error.
* <p>
* Handlers can themselves insert other handlers into the pipeline, using the {@link Context#insert(Handler...)} family of methods.
* <h3>Examples</h3>
* While there is no strict taxonomy of handlers, the following are indicative examples of common functions.
*
* <pre class="tested">
* import ratpack.core.handling.*;
*
* // A responder may just return a response to the client
*
* class SimpleHandler implements Handler {
* void handle(Context exchange) {
* exchange.getResponse().send("Hello World!");
* }
* }
*
* // A responder may add a response header, before delegating to the next in the pipeline
*
* class DecoratingHandler implements Handler {
* void handle(Context exchange) {
* exchange.getResponse().getHeaders().set("Cache-Control", "no-cache");
* exchange.next();
* }
* }
*
* // Or a handler may conditionally respond
*
* class ConditionalHandler implements Handler {
* void handle(Context exchange) {
* if (exchange.getRequest().getPath().equals("foo")) {
* exchange.getResponse().send("Hello World!");
* } else {
* exchange.next();
* }
* }
* }
*
* // A handler does not need to participate in the response, but can instead "route" the exchange to different handlers
*
* class RoutingHandler implements Handler {
* private final Handler[] fooHandlers;
*
* public RoutingHandler(Handler... fooHandlers) {
* this.fooHandlers = fooHandlers;
* }
*
* void handle(Context exchange) {
* if (exchange.getRequest().getPath().startsWith("foo/")) {
* exchange.insert(fooHandlers);
* } else {
* exchange.next();
* }
* }
* }
*
* // It can sometimes be appropriate to directly delegate to a handler, instead of using exchange.insert()
*
* class FilteringHandler implements Handler {
* private final Handler nestedHandler;
*
* public FilteringHandler(Handler nestedHandler) {
* this.nestedHandler = nestedHandler;
* }
*
* void handle(Context exchange) {
* if (exchange.getRequest().getPath().startsWith("foo/")) {
* nestedHandler.handle(exchange);
* } else {
* exchange.next();
* }
* }
* }
* </pre>
*
* @see Handlers
* @see Chain
* @see Registry
*/
@FunctionalInterface
public interface Handler {
/**
* Handles the context.
*
* @param ctx The context to handle
* @throws Exception if anything goes wrong (exception will be implicitly passed to the context's {@link Context#error(Throwable)} method)
*/
void handle(Context ctx) throws Exception;
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.http;
import ratpack.func.Nullable;
import ratpack.func.MultiValueMap;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Set;
/**
* An immutable set of HTTP headers.
*/
public interface Headers {
/**
* Returns the header value with the specified header name.
* <p>
* If there is more than one header value for the specified header name, the first value is returned.
*
* @param name The case insensitive name of the header to get retrieve the first value of
* @return the header value or {@code null} if there is no such header
*/
@Nullable
String get(CharSequence name);
/**
* Returns the header value with the specified header name.
* <p>
* If there is more than one header value for the specified header name, the first value is returned.
*
* @param name The case insensitive name of the header to get retrieve the first value of
* @return the header value or {@code null} if there is no such header
*/
@Nullable
String get(String name);
/**
* Returns the header value as a date with the specified header name.
* <p>
* If there is more than one header value for the specified header name, the first value is returned.
*
* @param name The case insensitive name of the header to get retrieve the first value of
* @return the header value as a date or {@code null} if there is no such header or the header value is not a valid date format
*/
@Nullable
Date getDate(CharSequence name);
/**
* Returns the header value as an instant with the specified header name.
* <p>
* If there is more than one header value for the specified header name, the first value is returned.
*
* @param name the case insensitive name of the header to get retrieve the first value of
* @return the header value as an instant or {@code null} if there is no such header or the header value is not a valid date format
* @since 1.4
*/
@Nullable
default Instant getInstant(CharSequence name) {
Date date = getDate(name);
return date == null ? null : Instant.ofEpochMilli(date.getTime());
}
/**
* Returns the header value as a date with the specified header name.
* <p>
* If there is more than one header value for the specified header name, the first value is returned.
*
* @param name The case insensitive name of the header to get retrieve the first value of
* @return the header value as a date or {@code null} if there is no such header or the header value is not a valid date format
*/
@Nullable
Date getDate(String name);
/**
* Returns all of the header values with the specified header name.
*
* @param name The case insensitive name of the header to retrieve all of the values of
* @return the {@link java.util.List} of header values, or an empty list if there is no such header
*/
List<String> getAll(CharSequence name);
/**
* Returns all of the header values with the specified header name.
*
* @param name The case insensitive name of the header to retrieve all of the values of
* @return the {@link java.util.List} of header values, or an empty list if there is no such header
*/
List<String> getAll(String name);
/**
* Checks whether a header has been specified for the given value.
*
* @param name The name of the header to check the existence of
* @return True if there is a header with the specified header name
*/
boolean contains(CharSequence name);
/**
* Checks whether a header has been specified for the given value.
*
* @param name The name of the header to check the existence of
* @return True if there is a header with the specified header name
*/
boolean contains(String name);
/**
* All header names.
*
* @return The names of all headers that were sent
*/
Set<String> getNames();
MultiValueMap<String, String> asMultiValueMap();
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.http;
import com.google.common.collect.ImmutableListMultimap;
import ratpack.func.Nullable;
/**
* A structured value for a Content-Type header value.
* <p>
* Can also represent a non existent (i.e. empty) value.
*/
@SuppressWarnings("UnusedDeclaration")
public interface MediaType {
/**
* {@value}.
*/
String PLAIN_TEXT_UTF8 = "text/plain;charset=utf-8";
/**
* {@value}.
*/
String APPLICATION_JSON = "application/json";
/**
* {@value}.
*/
String JSON_SUFFIX = "+json";
/**
* {@value}.
*/
String APPLICATION_FORM = "application/x-www-form-urlencoded";
/**
* {@value}.
*/
String TEXT_HTML = "text/html";
/**
* The type without parameters.
* <p>
* Given a mime type of "text/plain;charset=utf-8", returns "text/plain".
* <p>
* May be null to represent no content type.
*
* @return The mime type without parameters, or null if this represents the absence of a value.
*/
@Nullable
String getType();
/**
* The multimap containing parameters of the mime type.
* <p>
* Given a mime type of "application/json;charset=utf-8", the {@code get("charset")} returns {@code ["utf-8"]}".
* May be empty, never null.
* <p>
* All param names have been lower cased. The {@code charset} parameter values has been lower cased too.
*
* @return the immutable multimap of the media type params.
*/
ImmutableListMultimap<String, String> getParams();
/**
* The value of the "charset" parameter.
*
* @return the value of the charset parameter, or {@code null} if the no charset parameter was specified
*/
@Nullable
String getCharset();
/**
* The value of the "charset" parameter, or the given default value of no charset was specified.
*
* @param defaultValue the value if this type has no charset
* @return the value of the charset parameter, or the given default
*/
String getCharset(String defaultValue);
/**
* True if this type starts with "{@code text/}".
*
* @return True if this type starts with "{@code text/}".
*/
boolean isText();
/**
* True if this type equals {@value #APPLICATION_JSON}, or ends with {@value #JSON_SUFFIX}.
*
* @return if this represents a JSON like type
*/
boolean isJson();
/**
* True if this type equals {@value #APPLICATION_FORM}.
*
* @return True if this type equals {@value #APPLICATION_FORM}.
*/
boolean isForm();
/**
* True if this type equals {@value #TEXT_HTML}.
*
* @return True if this type equals {@value #TEXT_HTML}.
*/
boolean isHtml();
/**
* True if this represents the absence of a value (i.e. no Content-Type header)
*
* @return True if this represents the absence of a value (i.e. no Content-Type header)
*/
boolean isEmpty();
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.http;
import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
/**
* A set of HTTP headers that can be changed.
*/
public interface MutableHeaders extends Headers {
/**
* Adds a new header with the specified name and value.
* <p>
* Will not replace any existing values for the header.
* <p>
* Objects of type {@link Instant}, {@link Calendar} or {@link Date} will be converted to a
* RFC 7231 date/time string.
*
* @param name The name of the header
* @param value The value of the header
* @return this
*/
MutableHeaders add(CharSequence name, Object value);
/**
* Sets the (only) value for the header with the specified name.
* <p>
* All existing values for the same header will be removed.
* <p>
* Objects of type {@link Instant}, {@link Calendar} or {@link Date} will be converted to a
* RFC 7231 date/time string.
*
* @param name The name of the header
* @param value The value of the header
* @return this
*/
MutableHeaders set(CharSequence name, Object value);
/**
* Set a header with the given date as the value.
*
* @param name The name of the header
* @param value The date value
* @return this
*/
MutableHeaders setDate(CharSequence name, Date value);
/**
* Set a header with the given date as the value.
*
* @param name the name of the header
* @param value the date value
* @return this
* @since 1.4
*/
default MutableHeaders setDate(CharSequence name, Instant value) {
return setDate(name, new Date(value.toEpochMilli()));
}
/**
* Sets a new header with the specified name and values.
* <p>
* All existing values for the same header will be removed.
* <p>
* Objects of type {@link Instant}, {@link Calendar} or {@link Date} will be converted to a
* RFC 7231 date/time string.
*
* @param name The name of the header
* @param values The values of the header
* @return this
*/
MutableHeaders set(CharSequence name, Iterable<?> values);
/**
* Removes the header with the specified name.
*
* @param name The name of the header to remove.
* @return this
*/
MutableHeaders remove(CharSequence name);
/**
* Removes all headers from this message.
*
* @return this
*/
MutableHeaders clear();
MutableHeaders copy(Headers headers);
}

View File

@@ -0,0 +1,361 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.http;
import com.google.common.reflect.TypeToken;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.cookie.Cookie;
import ratpack.exec.Promise;
import ratpack.exec.stream.TransformablePublisher;
import ratpack.func.MultiValueMap;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A request to be handled.
*/
public interface Request {
/**
* A type token for this type.
*
* @since 1.1
*/
TypeToken<Request> TYPE = null;
/**
* The raw URI of the request.
* <p>
* This value may be an absolute URI or an absolute path.
*
* @return The raw URI of the request.
*/
String getRawUri();
/**
* The complete URI of the request (path + query string).
* <p>
* This value is always absolute (i.e. begins with "{@code /}").
*
* @return The complete URI of the request (path + query string).
*/
String getUri();
/**
* The query string component of the request URI, without the "?".
* <p>
* If the request does not contain a query component, an empty string will be returned.
*
* @return The query string component of the request URI, without the "?".
*/
String getQuery();
/**
* The URI without the query string and leading forward slash.
*
* @return The URI without the query string and leading forward slash
*/
String getPath();
/**
* TBD.
*
* @return TBD.
*/
MultiValueMap<String, String> getQueryParams();
/**
* The cookies that were sent with the request.
* <p>
* An empty set will be returned if no cookies were sent.
*
* @return The cookies that were sent with the request.
*/
Set<Cookie> getCookies();
/**
* Returns the value of the cookie with the specified name if it was sent.
* <p>
* If there is more than one cookie with this name, this method will throw an exception.
*
* @param name The name of the cookie to get the value of
* @return The cookie value, or null if not present
*/
@Nullable
String oneCookie(String name);
/**
* The body of the request.
* <p>
* If this request does not have a body, a non null object is still returned but it effectively has no data.
* <p>
* If the transmitted content is larger than provided {@link ServerConfig#getMaxContentLength()}, the given block will be invoked.
* If the block completes successfully, the promise will be terminated.
* If the block errors, the promise will carry the failure.
* <p>
* If the body is larger then {@link #getMaxContentLength()}, a {@link RequestBodyTooLargeException} will be propagated.
*
* @return the body of the request
*/
Promise<TypedData> getBody();
/**
* Overrides the idle timeout for this connection.
* <p>
* The default is set as part of server config via {@link ServerConfigBuilder#idleTimeout(Duration)}.
* <p>
* The override strictly applies to only the request/response exchange that this request is involved in.
*
* @param idleTimeout the idle timeout ({@link Duration#ZERO} = no timeout, must not be negative, must not be null)
* @since 1.5
* @see ServerConfig#getIdleTimeout()
*/
void setIdleTimeout(Duration idleTimeout);
/**
* The body of the request allowing up to the provided size for the content.
* <p>
* If this request does not have a body, a non null object is still returned but it effectively has no data.
* <p>
* If the transmitted content is larger than the provided {@code maxContentLength}, an {@code 413} client error will be issued.
*
* @param maxContentLength the maximum number of bytes allowed for the request.
* @return the body of the request.
* @since 1.1
*/
Promise<TypedData> getBody(long maxContentLength);
/**
* Allows reading the body as a stream, with back pressure.
* <p>
* Similar to {@link #getBodyStream(long)}, except uses {@link ServerConfig#getMaxContentLength()} as the max content length.
*
* @return a publisher of the request body
* @see #getBodyStream(long)
* @since 1.2
*/
TransformablePublisher<? extends ByteBuf> getBodyStream();
/**
* Allows reading the body as a stream, with back pressure.
* <p>
* The returned publisher emits the body as {@link ByteBuf}s.
* The subscriber <b>MUST</b> {@code release()} each emitted byte buf.
* Failing to do so will leak memory.
* <p>
* If the request body is larger than the given {@code maxContentLength}, a {@link RequestBodyTooLargeException} will be emitted.
* If the request body has already been read, a {@link RequestBodyAlreadyReadException} will be emitted.
* <p>
* The returned publisher is bound to the calling execution via {@link Streams#bindExec(Publisher)}.
* If your subscriber's onNext(), onComplete() or onError() methods are asynchronous they <b>MUST</b> use {@link Promise},
* {@link Blocking} or similar execution control constructs.
* <p>
* The following demonstrates how to use this method to stream the request body to a file, using asynchronous IO.
*
* <pre class="java">{@code
* import com.google.common.io.Files;
* import io.netty.buffer.ByteBuf;
* import org.apache.commons.lang3.RandomStringUtils;
* import org.reactivestreams.Subscriber;
* import org.reactivestreams.Subscription;
* import org.junit.Assert;
* import ratpack.exec.Promise;
* import ratpack.core.http.client.ReceivedResponse;
* import ratpack.test.embed.EmbeddedApp;
* import ratpack.test.embed.EphemeralBaseDir;
*
* import java.io.IOException;
* import java.nio.channels.AsynchronousFileChannel;
* import java.nio.charset.StandardCharsets;
* import java.nio.file.Path;
* import java.nio.file.StandardOpenOption;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* EphemeralBaseDir.tmpDir().use(dir -> {
* String string = RandomStringUtils.random(1024 * 1024);
* int length = string.getBytes(StandardCharsets.UTF_8).length;
*
* Path file = dir.path("out.txt");
*
* EmbeddedApp.fromHandler(ctx ->
* ctx.getRequest().getBodyStream(length).subscribe(new Subscriber<ByteBuf>() {
*
* private Subscription subscription;
* private AsynchronousFileChannel out;
* long written;
*
* {@literal @}Override
* public void onSubscribe(Subscription s) {
* subscription = s;
* try {
* this.out = AsynchronousFileChannel.open(
* file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING
* );
* subscription.request(1);
* } catch (IOException e) {
* subscription.cancel();
* ctx.error(e);
* }
* }
*
* {@literal @}Override
* public void onNext(ByteBuf byteBuf) {
* Promise.<Integer>async(down ->
* out.write(byteBuf.nioBuffer(), written, null, down.completionHandler())
* ).onError(error -> {
* byteBuf.release();
* subscription.cancel();
* out.close();
* ctx.error(error);
* }).then(bytesWritten -> {
* byteBuf.release();
* written += bytesWritten;
* subscription.request(1);
* });
* }
*
* {@literal @}Override
* public void onError(Throwable t) {
* ctx.error(t);
* try {
* out.close();
* } catch (IOException ignore) {
* // ignore
* }
* }
*
* {@literal @}Override
* public void onComplete() {
* try {
* out.close();
* } catch (IOException ignore) {
* // ignore
* }
* ctx.render("ok");
* }
* })
* ).test(http -> {
* ReceivedResponse response = http.request(requestSpec -> requestSpec.method("POST").getBody().text(string));
* Assert.assertEquals(response.getBody().getText(), "ok");
*
* String fileContents = Files.asCharSource(file.toFile(), StandardCharsets.UTF_8).read();
* Assert.assertEquals(fileContents, string);
* });
* });
* }
* }
* }</pre>
*
* @return a publisher of the request body
* @see #getBodyStream(long)
* @since 1.2
*/
TransformablePublisher<? extends ByteBuf> getBodyStream(long maxContentLength);
/**
* The request headers.
*
* @return The request headers.
*/
Headers getHeaders();
/**
* The type of the data as specified in the {@code "content-type"} header.
* <p>
* If no {@code "content-type"} header is specified, an empty {@link MediaType} is returned.
*
* @return The type of the data.
* @see MediaType#isEmpty()
*/
MediaType getContentType();
/**
* A flag representing whether or not the request originated via AJAX.
* @return A flag representing whether or not the request originated via AJAX.
*/
boolean isAjaxRequest();
/**
* Whether this request contains a {@code Expect: 100-Continue} header.
* <p>
* You do not need to send the {@code 100 Continue} status response.
* It will be automatically sent when the body is read via {@link #getBody()} or {@link #getBodyStream(long)} (or variants).
* Ratpack will not emit a {@code 417 Expectation Failed} response if the body is not read.
* The response specified by the handling code will not be altered, as per normal.
* <p>
* If the header is present for the request, this method will return {@code true} before and after the {@code 100 Continue} response has been sent.
*
* @return whether this request includes the {@code Expect: 100-Continue} header
* @since 1.2
*/
boolean isExpectsContinue();
/**
* Whether this request contains a {@code Transfer-Encoding: chunked} header.
* <p>
* See <a href="https://en.wikipedia.org/wiki/Chunked_transfer_encoding">https://en.wikipedia.org/wiki/Chunked_transfer_encoding</a>.
*
* @return whether this request contains a {@code Transfer-Encoding: chunked} header
* @since 1.2
*/
boolean isChunkedTransfer();
/**
* The advertised content length of the request body.
* <p>
* Will return {@code -1} if no {@code Content-Length} header is present, or is not valid long value.
*
* @return the advertised content length of the request body.
* @since 1.2
*/
long getContentLength();
/**
* The timestamp for when this request was received.
* Specifically, this is the timestamp of creation of the request object.
*
* @return the instant timestamp for the request.
*/
Instant getTimestamp();
/**
* Sets the allowed max content length for the request body.
* <p>
* This setting will be used when {@link #getBody()} or {@link #getBodyStream()} are called,
* and when implicitly reading the request body in order to respond (e.g. when issuing a response without trying to read the body).
*
* @param maxContentLength the maximum request body length in bytes
* @since 1.5
*/
void setMaxContentLength(long maxContentLength);
/**
* The max allowed content length for the request body.
*
* @see #setMaxContentLength(long)
* @return the maximum request body length in bytes
* @since 1.5
*/
long getMaxContentLength();
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.http;
import com.google.common.reflect.TypeToken;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.cookie.Cookie;
import ratpack.core.handling.Context;
import ratpack.exec.api.NonBlocking;
import java.nio.file.Path;
import java.util.Set;
import java.util.function.Supplier;
/**
* A response to a request.
* <p>
* The headers and status are configured, before committing the response with one of the {@link #send} methods.
*/
public interface Response {
/**
* A type token for this type.
*
* @since 1.1
*/
TypeToken<Response> TYPE = null;
/**
* Creates a new cookie with the given name and value.
* <p>
* The cookie will have no expiry. Use the returned cookie object to fine tune the cookie.
*
* @param name The name of the cookie
* @param value The value of the cookie
* @return The cookie that will be sent
*/
Cookie cookie(String name, String value);
/**
* Adds a cookie to the response with a 0 max-age, forcing the client to expire it.
* <p>
* If the cookie that you want to expire has an explicit path, you must use {@link Cookie#setPath(String)} on the return
* value of this method to have the cookie expire.
*
* @param name The name of the cookie to expire.
* @return The created cookie
*/
Cookie expireCookie(String name);
/**
* The cookies that are to be part of the response.
* <p>
* The cookies are mutable.
*
* @return The cookies that are to be part of the response.
*/
Set<Cookie> getCookies();
/**
* The response headers.
*
* @return The response headers.
*/
MutableHeaders getHeaders();
/**
* Sets the status line of the response.
* <p>
* The message used will be the standard for the code.
*
* @param code The status code of the response to use when it is sent.
* @return This
*/
default Response status(int code) {
return null;
}
/**
* Sends the response back to the client, with no body.
*/
@NonBlocking
void send();
Response contentTypeIfNotSet(Supplier<CharSequence> contentType);
/**
* Sends the response, using "{@code text/plain}" as the content type and the given string as the response body.
* <p>
* Equivalent to calling "{@code send\("text/plain", text)}.
*
* @param text The text to render as a plain text response.
*/
@NonBlocking
void send(String text);
/**
* Sends the response, using the given content type and string as the response body.
* <p>
* The string will be sent in "utf8" encoding, and the given content type will have this appended.
* That is, given a {@code contentType} of "{@code application/json}" the actual value for the {@code Content-Type}
* header will be "{@code application/json;charset=utf8}".
* <p>
* The value given for content type will override any previously set value for this header.
* @param contentType The value of the content type header
* @param body The string to render as the body of the response
*/
@NonBlocking
void send(CharSequence contentType, String body);
/**
* Sends the response, using "{@code application/octet-stream}" as the content type (if a content type hasn't
* already been set) and the given byte array as the response body.
*
* @param bytes The response body
*/
@NonBlocking
void send(byte[] bytes);
/**
* Sends the response, using the given content type and byte array as the response body.
* @param contentType The value of the {@code Content-Type} header
* @param bytes The response body
*/
@NonBlocking
void send(CharSequence contentType, byte[] bytes);
/**
* Sends the response, using "{@code application/octet-stream}" as the content type (if a content type hasn't
* already been set) and the given bytes as the response body.
*
* @param buffer The response body
*/
@NonBlocking
void send(ByteBuf buffer);
/**
* Sends the response, using the given content type and bytes as the response body.
* @param contentType The value of the {@code Content-Type} header
* @param buffer The response body
*/
@NonBlocking
void send(CharSequence contentType, ByteBuf buffer);
/**
* Sets the response {@code Content-Type} header.
*
* @param contentType The value of the {@code Content-Type} header
* @return This
*/
Response contentType(CharSequence contentType);
/**
* Sets the response {@code Content-Type} header, if it has not already been set.
*
* @param contentType The value of the {@code Content-Type} header
* @return This
*/
default Response contentTypeIfNotSet(CharSequence contentType) {
return null;
}
/**
* Sends the response, using the file as the response body.
* <p>
* This method does not set the content length, content type or anything else.
* It is generally preferable to use the {@link Context#render(Object)} method with a file/path object,
* or an {@link Chain#files(Action)}.
*
* @param file the response body
*/
@NonBlocking
void sendFile(Path file);
/**
* Prevents the response from being compressed.
*
* @return {@code this}
*/
Response noCompress();
/**
* Forces the closing of the current connection, even if the client requested it to be kept alive.
* <p>
* This method can be used when it is desirable to force the client's connection to close, defeating HTTP keep alive.
* This can be desirable in some networking environments where rate limiting or throttling is performed via edge routers or similar.
* <p>
* This method simply calls {@code getHeaders().set("Connection", "close")}, which has the same effect.
*
* @return {@code this}
* @since 1.1
*/
default Response forceCloseConnection() {
return null;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.http;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Data that potentially has a content type.
*/
public interface TypedData {
/**
* The type of the data.
*
* @return The type of the data.
*/
MediaType getContentType();
/**
* The data as text.
* <p>
* If a content type was provided, and it provided a charset parameter, that charset will be used to decode the text.
* If no charset was provided, {@code UTF-8} will be assumed.
* <p>
* This can lead to incorrect results for non {@code text/*} type content types.
* For example, {@code application/json} is implicitly {@code UTF-8} but this method will not know that.
*
* @return The data decoded as text
*/
String getText();
String getText(Charset charset);
/**
* The raw data as bytes.
*
* The returned array should not be modified.
*
* @return the raw data as bytes.
*/
byte[] getBytes();
/**
* The raw data as a (unmodifiable) buffer.
*
* @return the raw data as bytes.
*/
ByteBuf getBuffer();
/**
* Writes the data to the given output stream.
* <p>
* Data is effectively piped from {@link #getInputStream()} to the given output stream.
* As such, if the given output stream might block (e.g. is backed by a file or socket) then
* this should be called from a blocking thread using {@link Blocking#get(Factory)}
* This method does not flush or close the stream.
*
* @param outputStream The stream to write to
* @throws IOException any thrown when writing to the output stream
*/
void writeTo(OutputStream outputStream) throws IOException;
/**
* An input stream of the data.
* <p>
* This input stream is backed by an in memory buffer.
* Reading from this input stream will never block.
* <p>
* It is not necessary to close the input stream.
*
* @return an input stream of the data
*/
InputStream getInputStream();
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 ratpack.core.parse;
import com.google.common.reflect.TypeToken;
import ratpack.core.handling.Context;
import java.util.Optional;
/**
* The specification of a particular parse.
* <p>
* Construct instances via the {@link #of} methods.
*
* @param <T> the type of object to construct from the request body
* @param <O> the type of object that provides options/configuration for the parsing
* @see Context#parse(Parse)
* @see Parser
* @see ParserSupport
*/
public class Parse<T, O> {
/**
* The type of object to construct from the request body.
*
* @return the type of object to construct from the request body
*/
public TypeToken<T> getType() {
return null;
}
/**
* The type of object that provides options/configuration for the parsing.
* <p>
* For any parse request, no options may be specified.
* Parser implementations should throw an exception if they require an options object when none is supplied.
*
* @return the type of object that provides options/configuration for the parsing
*/
public Optional<O> getOpts() {
return null;
}
/**
* Creates a parse object.
*
* @param type the type of object to construct from the request body
* @param opts the options object
* @param <T> the type of object to construct from the request body
* @param <O> the type of object that provides options/configuration for the parsing
* @return a parse instance from the given arguments
*/
public static <T, O> Parse<T, O> of(TypeToken<T> type, O opts) {
return null;
}
/**
* Creates a parse object, with no options.
*
* @param type the type of object to construct from the request body
* @param <T> the type of object to construct from the request body
* @return a parse instance to the given type
*/
public static <T> Parse<T, ?> of(TypeToken<T> type) {
return null;
}
/**
* Creates a parse object.
*
* @param type the type of object to construct from the request body
* @param opts the options object
* @param <T> the type of object to construct from the request body
* @param <O> the type of object that provides options/configuration for the parsing
* @return a parse instance from the given arguments
*/
public static <T, O> Parse<T, O> of(Class<T> type, O opts) {
return null;
}
/**
* Creates a parse object, with no options.
*
* @param type the type of object to construct from the request body
* @param <T> the type of object to construct from the request body
* @return a parse instance to the given type
*/
public static <T> Parse<T, ?> of(Class<T> type) {
return null;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.core.render;
/**
* Thrown when a request is made to render an object, but no suitable renderer can be found.
*/
public class NoSuchRendererException extends RuntimeException {
private static final long serialVersionUID = 0;
/**
* Constructor.
*
* @param object The object to be rendered.
*/
public NoSuchRendererException(Object object) {
super("No renderer for object '" + object + "' of type '" + object.getClass() + "'");
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 ratpack.exec;
import ratpack.func.Action;
import ratpack.func.Factory;
import ratpack.func.Function;
import ratpack.func.Predicate;
/**
* A promise for a single value.
* <p>
* A promise is a representation of a value which will become available later.
* Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of "operations" to be specified,
* that the value will travel through as it becomes available.
* Such operations are implemented via the {@link #transform(Function)} method.
* Each operation returns a new promise object, not the original promise object.
* <p>
* To create a promise, use the {@link Promise#async(Upstream)} method (or one of the variants such as {@link Promise#sync(Factory)}.
* To test code that uses promises, use the {@link ratpack.test.exec.ExecHarness}.
* <p>
* The promise is not "activated" until the {@link #then(Action)} method is called.
* This method terminates the pipeline, and receives the final value.
* <p>
* Promise objects are multi use.
* Every promise pipeline has a value producing function at its start.
* Activating a promise (i.e. calling {@link #then(Action)}) invokes the function.
* The {@link #cache()} operation can be used to change this behaviour.
*
* @param <T> the type of promised value
*/
@SuppressWarnings("JavadocReference")
public interface Promise<T> {
static <T> Promise<T> sync(Factory<T> factory) {
return null;
}
static <T> Promise<T> flatten(Factory<? extends Promise<T>> factory) {
return null;
}
static <T> Promise<T> value(T t) {
return null;
}
<O> Promise<O> map(Function<? super T, ? extends O> transformer);
<O> Promise<O> flatMap(Function<? super T, ? extends Promise<O>> transformer);
void then(Action<? super T> then);
Promise<T> next(Action<? super T> action);
default <E extends Throwable> Promise<T> onError(Class<E> errorType, Action<? super E> errorHandler) {
return null;
}
default Promise<T> onError(Action<? super Throwable> errorHandler) {
return null;
}
default Promise<T> mapIf(Predicate<? super T> predicate, Function<? super T, ? extends T> transformer) {
return null;
}
default <O> Promise<O> mapIf(Predicate<? super T> predicate, Function<? super T, ? extends O> onTrue, Function<? super T, ? extends O> onFalse) {
return null;
}
default Promise<T> mapError(Function<? super Throwable, ? extends T> transformer) {
return null;
}
default <E extends Throwable> Promise<T> mapError(Class<E> type, Function<? super E, ? extends T> function) {
return null;
}
default Promise<T> mapError(Predicate<? super Throwable> predicate, Function<? super Throwable, ? extends T> function) {
return null;
}
default Promise<T> flatMapError(Function<? super Throwable, ? extends Promise<T>> function) {
return null;
}
default <E extends Throwable> Promise<T> flatMapError(Class<E> type, Function<? super E, ? extends Promise<T>> function) {
return null;
}
default Promise<T> flatMapError(Predicate<? super Throwable> predicate, Function<? super Throwable, ? extends Promise<T>> function) {
return null;
}
default Promise<T> route(Predicate<? super T> predicate, Action<? super T> action) {
return null;
}
default Promise<T> cache() {
return null;
}
default Promise<T> cacheIf(Predicate<? super T> shouldCache) {
return null;
}
default Promise<T> fork() {
return null;
}
default <O> Promise<O> apply(Function<? super Promise<T>, ? extends Promise<O>> function) {
return null;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.exec.api;
import java.lang.annotation.*;
/**
* Declares that a method or function-like method parameter is non-blocking and can freely use {@link ratpack.exec.Promise} and other async constructs.
* <p>
* If this annotation is present on a method, it indicates that the method may be asynchronous.
* That is, it is not necessarily expected to have completed its logical work when the method returns.
* The method must however use {@link ratpack.exec.Promise}, {@link ratpack.exec.Operation}, or other execution mechanisms to perform asynchronous work.
* <p>
* Most such methods are invoked as part of Ratpack.
* If you need to invoke such a method, do so as part of a discrete {@link ratpack.exec.Operation}.
* <p>
* If this annotation is present on a function type method parameter, it indicates that the annotated function may be asynchronous.
* Similarly, if you need to invoke such a parameter, do so as part of a discrete {@link ratpack.exec.Operation}.
* <p>
* <b>Note:</b> the ability to annotate method parameters with this annotation was added in version {@code 1.1.0}.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface NonBlocking {
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.exec.registry;
import com.google.common.reflect.TypeToken;
/**
* Thrown when a request is made for an object that a registry cannot provide.
*
* @see Registry#get(Class)
*/
public class NotInRegistryException extends RuntimeException {
private static final long serialVersionUID = 0;
/**
* Constructs the exception.
*
* @param type The requested type of the object
*/
public NotInRegistryException(TypeToken<?> type) {
this(String.format("No object for type '%s' in registry", type));
}
/**
* Constructor.
*
* @param message The exception message
*/
public NotInRegistryException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.exec.registry;
import com.google.common.reflect.TypeToken;
import java.util.Optional;
import java.util.function.Supplier;
/**
* An object registry.
* <p>
* Registries are primarily used for inter {@link ratpack.core.handling.Handler} communication in request processing.
* The {@link ratpack.core.handling.Context} object that handlers operate on implements the {@link Registry} interface.
* <pre class="tested">
* import ratpack.core.handling.Handler;
* import ratpack.core.handling.Context;
* import ratpack.exec.registry.Registry;
*
* import static org.junit.Assert.assertTrue;
*
* public class Thing {
* private final String name
* public Thing(String name) { this.name = name; }
* public String getName() { return name; }
* }
*
* public class UpstreamHandler implements Handler {
* public void handle(Context context) {
* context.next(Registry.single(new Thing("foo")));
* }
* }
*
* public class DownstreamHandler implements Handler {
* public void handle(Context context) {
* assertTrue(context instanceof Registry);
* Thing thing = context.get(Thing.class);
* context.render(thing.getName());
* }
* }
*
* import ratpack.test.handling.HandlingResult;
* import ratpack.test.handling.RequestFixture;
*
* import static ratpack.core.handling.Handlers.chain;
* import static ratpack.func.Action.noop;
*
* import static org.junit.Assert.assertEquals;
*
* Handler chain = chain(new UpstreamHandler(), new DownstreamHandler());
* HandlingResult result = RequestFixture.handle(chain, noop());
*
* assertEquals("foo", result.rendered(String.class));
* </pre>
* <h3>Thread safety</h3>
* <p>
* Registry objects are assumed to be thread safe.
* No external synchronization is performed around registry access.
* As registry objects may be used across multiple requests, they should be thread safe.
* <p>
* Registries that are <b>created</b> per request however do not need to be thread safe.
*
* <h3>Ordering</h3>
* <p>
* Registry objects are returned in the reverse order that they were added (i.e. Last-In-First-Out).
*
* <pre class="java">{@code
* import com.google.common.base.Joiner;
* import ratpack.exec.registry.Registry;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Registry registry = Registry.of(r -> r
* .add("Ratpack")
* .add("foo")
* .add("bar")
* );
*
* assertEquals("bar", registry.get(String.class));
*
* String joined = Joiner.on(", ").join(registry.getAll(String.class));
* assertEquals("bar, foo, Ratpack", joined);
* }
* }
* }</pre>
* <p>
* While this is strictly the case for the core registry implementations in Ratpack, adapted implementations (e.g. Guice, Spring etc.) may have more nuanced ordering semantics.
* To the greatest extent possible, registry implementations should strive to honor LIFO ordering.
*/
public interface Registry {
/**
* Provides an object of the specified type, or throws an exception if no object of that type is available.
*
* @param type The type of the object to provide
* @param <O> The type of the object to provide
* @return An object of the specified type
* @throws NotInRegistryException If no object of this type can be returned
*/
default <O> O get(Class<O> type) throws NotInRegistryException {
return null;
}
/**
* Provides an object of the specified type, or throws an exception if no object of that type is available.
*
* @param type The type of the object to provide
* @param <O> The type of the object to provide
* @return An object of the specified type
* @throws NotInRegistryException If no object of this type can be returned
*/
default <O> O get(TypeToken<O> type) throws NotInRegistryException {
return null;
}
/**
* Does the same thing as {@link #get(Class)}, except returns null instead of throwing an exception.
*
* @param type The type of the object to provide
* @param <O> The type of the object to provide
* @return An object of the specified type, or null if no object of this type is available.
*/
default <O> Optional<O> maybeGet(Class<O> type) {
return null;
}
/**
* Does the same thing as {@link #get(Class)}, except returns null instead of throwing an exception.
*
* @param type The type of the object to provide
* @param <O> The type of the object to provide
* @return An optional of an object of the specified type
*/
<O> Optional<O> maybeGet(TypeToken<O> type);
/**
* Returns all of the objects whose declared type is assignment compatible with the given type.
*
* @param type the type of objects to search for
* @param <O> the type of objects to search for
* @return All objects of the given type
*/
default <O> Iterable<? extends O> getAll(Class<O> type) {
return null;
}
/**
* Returns all of the objects whose declared type is assignment compatible with the given type.
*
* @param type the type of objects to search for
* @param <O> the type of objects to search for
* @return All objects of the given type
*/
<O> Iterable<? extends O> getAll(TypeToken<O> type);
/**
* Creates a new registry by joining {@code this} registry with the given registry
* <p>
* The returned registry is effectively the union of the two registries, with the {@code child} registry taking precedence.
* This means that child entries are effectively "returned first".
* <pre class="java">{@code
* import ratpack.exec.registry.Registry;
*
* import java.util.List;
* import com.google.common.collect.Lists;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static interface Thing {
* String getName();
* }
*
* public static class ThingImpl implements Thing {
* private final String name;
*
* public ThingImpl(String name) {
* this.name = name;
* }
*
* public String getName() {
* return name;
* }
* }
*
* public static void main(String[] args) {
* Registry child = Registry.builder().add(Thing.class, new ThingImpl("child-1")).add(Thing.class, new ThingImpl("child-2")).build();
* Registry parent = Registry.builder().add(Thing.class, new ThingImpl("parent-1")).add(Thing.class, new ThingImpl("parent-2")).build();
* Registry joined = parent.join(child);
*
* assertEquals("child-2", joined.get(Thing.class).getName());
* List<Thing> all = Lists.newArrayList(joined.getAll(Thing.class));
* assertEquals("child-2", all.get(0).getName());
* assertEquals("child-1", all.get(1).getName());
* assertEquals("parent-2", all.get(2).getName());
* assertEquals("parent-1", all.get(3).getName());
* }
* }
* }</pre>
*
* @param child the child registry
* @return a registry which is the combination of the {@code this} and the given child
*/
default Registry join(Registry child) {
return null;
}
/**
* Returns an empty registry.
*
* @return an empty registry
*/
static Registry empty() {
return null;
}
/**
* Creates a new {@link RegistryBuilder registry builder}.
*
* @return a new registry builder
* @see RegistryBuilder
*/
static RegistryBuilder builder() {
return null;
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 ratpack.exec.registry;
public interface RegistryBuilder {}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 ratpack.exec.stream;
/**
* A wrapper over a {@link Publisher} that makes it more convenient to chain transformations of different kinds.
* <p>
* Note that this type implements the publisher interface,
* so behaves just like the publisher that it is wrapping with respect to the
* {@link Publisher#subscribe(Subscriber)} method.
*
* @param <T> the type of item emitted by this publisher
*/
public interface TransformablePublisher<T> {
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.func;
import java.util.function.Consumer;
/**
* A generic type for an object that does some work with a thing.
* <p>
* This type serves the same purpose as the JDK's {@link java.util.function.Consumer}, but allows throwing checked exceptions.
* It contains methods for bridging to and from the JDK type.
*
* @param <T> The type of thing.
*/
@FunctionalInterface
public interface Action<T> {
/**
* Executes the action against the given thing.
*
* @param t the thing to execute the action against
* @throws Exception if anything goes wrong
*/
void execute(T t) throws Exception;
/**
* Returns an action that does precisely nothing.
*
* @return an action that does precisely nothing
*/
static <T> Action<T> noop() {
return null;
}
/**
* If the given action is {@code null}, returns {@link #noop()}, otherwise returns the given action.
*
* @param action an action, maybe {@code null}.
* @param <T> the type of parameter received by the action
* @return the given {@code action} param if it is not {@code null}, else a {@link #noop()}.
*/
static <T> Action<? super T> noopIfNull(@Nullable Action<T> action) {
return null;
}
/**
* Returns a new action that executes the given actions in order.
*
* @param actions the actions to join into one action
* @param <T> the type of object the action accepts
* @return the newly created aggregate action
*/
@SafeVarargs
static <T> Action<T> join(final Action<? super T>... actions) {
return null;
}
/**
* Returns a new action that executes this action and then the given action.
*
* @param action the action to execute after this action
* @param <O> the type of object the action accepts
* @return the newly created aggregate action
*/
default <O extends T> Action<O> append(Action<? super O> action) {
return null;
}
/**
* Returns a new action that executes the given action and then this action.
*
* @param action the action to execute before this action
* @param <O> the type of object the action accepts
* @return the newly created aggregate action
*/
default <O extends T> Action<O> prepend(Action<? super O> action) {
return null;
}
/**
* Returns an action that receives a throwable and immediately throws it.
*
* @return an action that receives a throwable and immediately throws it
*/
static Action<Throwable> throwException() {
return null;
}
/**
* An action that receives a throwable to thrown, suppressing the given value.
*
* @return an action that receives a throwable to thrown, suppressing the given value
* @since 1.5
*/
static Action<Throwable> suppressAndThrow(Throwable toSuppress) {
return null;
}
/**
* Returns an action that immediately throws the given exception.
* <p>
* The exception is thrown via {@link Exceptions#toException(Throwable)}
*
* @param <T> the argument type (anything, as the argument is ignored)
* @param throwable the throwable to immediately throw when the returned action is executed
* @return an action that immediately throws the given exception.
*/
static <T> Action<T> throwException(final Throwable throwable) {
return null;
}
/**
* Executes the action with the given argument, then returns the argument.
* <pre class="java">{@code
* import ratpack.func.Action;
* import java.util.ArrayList;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* assertEquals("foo", Action.with(new ArrayList<>(), list -> list.add("foo")).get(0));
* }
* }
* }</pre>
* @param t the argument to execute the given action with
* @param action the action to execute with the given argument
* @param <T> the type of the argument
* @return the given argument (i.e. {@code t})
* @throws Exception any thrown by {@code action}
*/
static <T> T with(T t, Action<? super T> action) throws Exception {
return null;
}
/**
* Executes with the given argument, then returns the argument.
* <pre class="java">{@code
* import ratpack.func.Action;
* import java.util.List;
* import java.util.ArrayList;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* assertEquals("foo", run(list -> list.add("foo")).get(0));
* }
*
* private static List<String> run(Action<? super List<String>> action) throws Exception {
* return action.with(new ArrayList<>());
* }
* }
* }</pre>
* @param o the argument to execute the given action with
* @param <O> the type of the argument
* @return the given argument (i.e. {@code o})
* @throws Exception any thrown by {@link #execute(Object)}
*/
default <O extends T> O with(O o) throws Exception {
return null;
}
/**
* Like {@link #with(Object, Action)}, but unchecks any exceptions thrown by the action via {@link Exceptions#uncheck(Throwable)}.
*
* @param t the argument to execute the given action with
* @param action the action to execute with the given argument
* @param <T> the type of the argument
* @return the given argument (i.e. {@code t})
*/
static <T> T uncheckedWith(T t, Action<? super T> action) {
return null;
}
/**
* Like {@link #with(Object)}, but unchecks any exceptions thrown by the action via {@link Exceptions#uncheck(Throwable)}.
*
* @param o the argument to execute with
* @param <O> the type of the argument
* @return the given argument (i.e. {@code o})
*/
default <O extends T> O uncheckedWith(O o) {
return null;
}
/**
* Creates a JDK {@link Consumer} from this action.
* <p>
* Any exceptions thrown by {@code this} action will be unchecked via {@link Exceptions#uncheck(Throwable)} and rethrown.
*
* @return this function as a JDK style consumer.
*/
default Consumer<T> toConsumer() {
return null;
}
/**
* Creates an exception-taking action that executes the given action before throwing the exception.
*
* @param action the action to perform before throwing the exception
* @return an action that performs the given action before throwing its argument
* @since 1.5
*/
static Action<Throwable> beforeThrow(Action<? super Throwable> action) {
return null;
}
/**
* Creates an action that delegates to the given action if the given predicate applies, else delegates to {@link #noop()}.
* <p>
* This is equivalent to {@link #when(Predicate, Action, Action) when(predicate, action, noop())}.
*
* @param predicate the condition for the argument
* @param action the action to execute if the predicate applies
* @param <I> the type of argument
* @return an action that delegates to the given action if the predicate applies, else noops
* @see #when(Predicate, Action, Action)
* @see #conditional(Action, Action)
* @since 1.5
*/
static <I> Action<I> when(Predicate<? super I> predicate, Action<? super I> action) {
return null;
}
/**
* Creates an action that delegates to the first action if the given predicate applies, else the second action.
*
* @param predicate the condition for the argument
* @param onTrue the action to execute if the predicate applies
* @param onFalse the action to execute if the predicate DOES NOT apply
* @param <I> the type of argument
* @return an action that delegates to the first action if the predicate applies, else the second argument
* @see #when(Predicate, Action)
* @see #conditional(Action, Action)
* @since 1.5
*/
static <I> Action<I> when(Predicate<? super I> predicate, Action<? super I> onTrue, Action<? super I> onFalse) {
return null;
}
/**
* A spec for adding conditions to a conditional action.
*
* @param <I> the input type
* @see #conditional(Action, Action)
* @since 1.5
*/
interface ConditionalSpec<I> {
ConditionalSpec<I> when(Predicate<? super I> predicate, Action<? super I> action);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.func;
/**
* An object that creates another.
*
* Factories are expected to create a new object each time.
* Implementors should explain there behaviour if they do not do this.
*
* @param <T> the type of object that this factory creates
*/
@FunctionalInterface
public interface Factory<T> {
/**
* Creates a new object.
*
* @return a newly created object
* @throws Exception any
*/
T create() throws Exception;
/**
* Creates a factory that always returns the given item.
*
* @param item the item to always provide
* @param <T> the type of the item
* @return a factory that always returns {@code item}
* @since 1.1
*/
static <T> Factory<T> constant(T item) {
return () -> item;
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.func;
import java.util.Objects;
/**
* A single argument function.
* <p>
* This type serves the same purpose as the JDK's {@link java.util.function.Function}, but allows throwing checked exceptions.
* It contains methods for bridging to and from the JDK type.
*
* @param <I> the type of the input
* @param <O> the type of the output
*/
@FunctionalInterface
public interface Function<I, O> {
/**
* The function implementation.
*
* @param i the input to the function
* @return the output of the function
* @throws Exception any
*/
O apply(I i) throws Exception;
/**
* Joins {@code this} function with the given function.
*
* <pre class="java">{@code
* import ratpack.func.Function;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* Function<String, String> function = in -> in + "-bar";
* assertEquals("FOO-BAR", function.andThen(String::toUpperCase).apply("foo"));
* }
* }
* }</pre>
* <p>
* Analogous to {@link java.util.function.Function#andThen(java.util.function.Function)}.
*
* @param after the function to apply to the result of {@code this} function
* @param <T> the type of the final output
* @return the result of applying the given function to {@code this} function
* @throws Exception any thrown by {@code this} or {@code after}
*/
default <T> Function<I, T> andThen(Function<? super O, ? extends T> after) throws Exception {
return null;
}
/**
* Joins the given function with {@code this} function.
*
* <pre class="java">{@code
* import ratpack.func.Function;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Function<String, String> function = String::toUpperCase;
* assertEquals("FOO-BAR", function.compose(in -> in + "-BAR").apply("foo"));
* }
* }
* }</pre>
* <p>
* Analogous to {@link java.util.function.Function#compose(java.util.function.Function)}.
*
* @param before the function to apply {@code this} function to the result of
* @param <T> the type of the new input
* @return the result of applying {@code this} function to the result of the given function
* @throws Exception any thrown by {@code this} or {@code before}
*/
default <T> Function<T, O> compose(Function<? super T, ? extends I> before) throws Exception {
return null;
}
/**
* Returns an identity function (return value always same as input).
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return null;
}
/**
* Returns a function that <i>always</i> returns the given argument.
*
* @param t the value to always return
* @param <T> the type of returned value
* @return a function that returns the given value
*/
static <T> Function<Object, T> constant(T t) {
return null;
}
/**
* Creates a function that delegates to the given function if the given predicate applies, else delegates to {@link #identity()}.
* <p>
* This is equivalent to {@link #when(Predicate, Function, Function) when(predicate, function, identity())}.
*
* @param predicate the condition for the argument
* @param function the function to apply if the predicate applies
* @param <I> the type of argument and return value
* @return a function that delegates to the given function if the predicate applies, else returns the argument
* @see #when(Predicate, Function, Function)
* @see #conditional(Function, Action)
* @since 1.5
*/
static <I> Function<I, I> when(Predicate<? super I> predicate, Function<? super I, ? extends I> function) {
return null;
}
/**
* Creates a function that delegates to the first function if the given predicate applies, else the second function.
*
* @param predicate the condition for the argument
* @param onTrue the function to apply if the predicate applies
* @param onFalse the function to apply if the predicate DOES NOT apply
* @param <I> the type of argument
* @param <O> the type of return value
* @return a function that delegates to the first function if the predicate applies, else the second argument
* @see #when(Predicate, Function)
* @see #conditional(Function, Action)
* @since 1.5
*/
static <I, O> Function<I, O> when(Predicate<? super I> predicate, Function<? super I, ? extends O> onTrue, Function<? super I, ? extends O> onFalse) {
return null;
}
/**
* A spec for adding conditions to a conditional function.
*
* @param <I> the input type
* @param <O> the output type
* @see #conditional(Function, Action)
* @since 1.5
*/
interface ConditionalSpec<I, O> {
/**
* Adds a conditional function.
*
* @param predicate the condition predicate
* @param function the function to apply if the predicate applies
* @return {@code this}
*/
ConditionalSpec<I, O> when(Predicate<? super I> predicate, Function<? super I, ? extends O> function);
}
/**
* Creates a function that delegates based on the specified conditions.
* <p>
* If no conditions match, an {@link IllegalArgumentException} will be thrown.
* Use {@link #conditional(Function, Action)} alternatively to specify a different "else" strategy.
*
* @param conditions the conditions
* @param <I> the input type
* @param <O> the output type
* @return a conditional function
* @see #conditional(Function, Action)
* @throws Exception any thrown by {@code conditions}
* @since 1.5
*/
static <I, O> Function<I, O> conditional(Action<? super ConditionalSpec<I, O>> conditions) throws Exception {
return null;
}
/**
* Creates a function that delegates based on the specified conditions.
* <p>
* If no condition applies, the {@code onElse} function will be delegated to.
*
* @param onElse the function to delegate to if no condition matches
* @param conditions the conditions
* @param <I> the input type
* @param <O> the output type
* @return a conditional function
* @see #conditional(Action)
* @throws Exception any thrown by {@code conditions}
* @since 1.5
*/
static <I, O> Function<I, O> conditional(Function<? super I, ? extends O> onElse, Action<? super ConditionalSpec<I, O>> conditions) throws Exception {
return null;
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.func;
import com.google.common.collect.ListMultimap;
import java.util.List;
import java.util.Map;
/**
* A map that may contain multiple values for a given key, but typically only one value.
* <p>
* Unlike other multi map types, this type is optimized for the case where there is only one value for a key.
* The map acts just like a normal {@link Map}, but has extra methods for getting all values for a key.
* <p>
* <b>All implementations of this type are immutable.</b> Mutating operations throw {@link UnsupportedOperationException}.
* <p>
* Where there is multiple values for a given key, retrieving a single value will return the <i>first</i> value,
* where the first value is intrinsic to the service in which the map is being used.
*
* @param <K> The type of key objects
* @param <V> The type of value objects
*/
public interface MultiValueMap<K, V> extends Map<K, V> {
static <K, V> MultiValueMap<K, V> empty() {
return null;
}
/**
* All of the values for the given key. An empty list if there are no values for the key.
* <p>
* The returned list is immutable.
*
* @param key The key to return all values of
* @return all of the values for the given key, or an empty list if there are no values for the key.
*/
List<V> getAll(K key);
/**
* Returns a new view of the map where each map value is a list of all the values for the given key (i.e. a traditional multi map).
* <p>
* The returned map is immutable.
* @return A new view of the map where each map value is a list of all the values for the given key
*/
Map<K, List<V>> getAll();
/**
* Get the first value for the key, or {@code null} if there are no values for the key.
*
* @param key The key to obtain the first value for
* @return The first value for the given key, or {@code null} if there are no values for the given key
*/
V get(Object key);
/**
* Throws {@link UnsupportedOperationException}.
*
* {@inheritDoc}
*/
V put(K key, V value);
/**
* Throws {@link UnsupportedOperationException}.
*
* {@inheritDoc}
*/
V remove(Object key);
/**
* Throws {@link UnsupportedOperationException}.
*
* {@inheritDoc}
*/
@SuppressWarnings("NullableProblems")
void putAll(Map<? extends K, ? extends V> m);
/**
* Throws {@link UnsupportedOperationException}.
*
* {@inheritDoc}
*/
void clear();
ListMultimap<K, V> asMultimap();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.func;
import java.lang.annotation.*;
/**
* Denotes that something may be null.
* <ul>
* <li>On a <b>parameter</b>, denotes that it is valid to supply null as the value for the parameter.
* <li>On a <b>method</b>, denotes that the method may return null.
* <li>On a <b>field</b>, denotes that the field value may be null.
* </ul>
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
public @interface Nullable {
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 ratpack.func;
/**
* A function that returns {@code true} or {@code false} for a value.
* <p>
* This type serves the same purpose as the JDK's {@link java.util.function.Predicate}, but allows throwing checked exceptions.
* It contains methods for bridging to and from the JDK type.
*
* @param <T> the type of object "tested" by the predicate
*/
@FunctionalInterface
public interface Predicate<T> {
/**
* Tests the given value.
*
* @param t the value to "test"
* @return {@code true} if the predicate applied, otherwise {@code false}
* @throws Exception any
*/
boolean apply(T t) throws Exception;
/**
* Creates a predicate from a JDK predicate.
*
* @param predicate the JDK predicate
* @param <T> the type of object this predicate tests
* @return the given JDK predicate as a predicate
*/
static <T> Predicate<T> from(java.util.function.Predicate<T> predicate) {
return null;
}
/**
* Creates a predicate from a Guava predicate.
*
* @param predicate the Guava predicate
* @param <T> the type of object this predicate tests
* @return the given Guava predicate as a predicate
*/
static <T> Predicate<T> fromGuava(com.google.common.base.Predicate<T> predicate) {
return null;
}
/**
* A predicate that always returns {@code true}, regardless of the input object.
*
* @param <T> the type of input object
* @return a predicate that always returns {@code true}
* @since 1.1
*/
static <T> Predicate<T> alwaysTrue() {
return null;
}
/**
* A predicate that always returns {@code false}, regardless of the input object.
*
* @param <T> the type of input object
* @return a predicate that always returns {@code false}
* @since 1.1
*/
static <T> Predicate<T> alwaysFalse() {
return null;
}
/**
* Creates a function the returns one of the given values.
*
* @param onTrue the value to return if the predicate applies
* @param onFalse the value to return if the predicate does not apply
* @param <O> the output value
* @return a function
* @since 1.5
*/
default <O> Function<T, O> function(O onTrue, O onFalse) {
return null;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.jackson;
import ratpack.func.Nullable;
import ratpack.core.parse.Parse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class Jackson {
private Jackson() {
}
public static Parse<JsonNode, JsonParseOpts> jsonNode(@Nullable ObjectMapper objectMapper) {
return null;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed 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 ratpack.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Optional;
public interface JsonParseOpts {
Optional<ObjectMapper> getObjectMapper();
}