Skip to content

Commit 249fe6b

Browse files
committed
Introduce Result class
1 parent 05c8901 commit 249fe6b

File tree

6 files changed

+200
-54
lines changed

6 files changed

+200
-54
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.common.util;
11+
12+
import org.elasticsearch.common.CheckedSupplier;
13+
14+
import java.util.Optional;
15+
16+
/**
17+
* A wrapper around either
18+
* <ul>
19+
* <li>a successful result of parameterized type {@code V}</li>
20+
* <li>a failure with exception type {@code E}</li>
21+
* </ul>
22+
*/
23+
public abstract class Result<V, E extends Exception> implements CheckedSupplier<V, E> {
24+
25+
public static <V, E extends Exception> Result<V, E> of(V value) {
26+
return new Success<>(value);
27+
}
28+
29+
public static <V, E extends Exception> Result<V, E> failure(E exception) {
30+
return new Failure<>(exception);
31+
}
32+
33+
private Result() {}
34+
35+
public abstract V get() throws E;
36+
37+
public abstract Optional<E> failure();
38+
39+
public abstract boolean isSuccessful();
40+
41+
public boolean isFailure() {
42+
return isSuccessful() == false;
43+
};
44+
45+
public abstract Optional<V> asOptional();
46+
47+
private static class Success<V, E extends Exception> extends Result<V, E> {
48+
private final V value;
49+
50+
Success(V value) {
51+
this.value = value;
52+
}
53+
54+
@Override
55+
public V get() throws E {
56+
return value;
57+
}
58+
59+
@Override
60+
public Optional<E> failure() {
61+
return Optional.empty();
62+
}
63+
64+
@Override
65+
public boolean isSuccessful() {
66+
return true;
67+
}
68+
69+
@Override
70+
public Optional<V> asOptional() {
71+
return Optional.of(value);
72+
}
73+
}
74+
75+
private static class Failure<V, E extends Exception> extends Result<V, E> {
76+
private final E exception;
77+
78+
Failure(E exception) {
79+
this.exception = exception;
80+
}
81+
82+
@Override
83+
public V get() throws E {
84+
throw exception;
85+
}
86+
87+
@Override
88+
public Optional<E> failure() {
89+
return Optional.of(exception);
90+
}
91+
92+
@Override
93+
public boolean isSuccessful() {
94+
return false;
95+
}
96+
97+
@Override
98+
public Optional<V> asOptional() {
99+
return Optional.empty();
100+
}
101+
}
102+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.common.util;
11+
12+
import org.elasticsearch.ElasticsearchException;
13+
import org.elasticsearch.ElasticsearchStatusException;
14+
import org.elasticsearch.rest.RestStatus;
15+
import org.elasticsearch.test.ESTestCase;
16+
17+
import static org.elasticsearch.test.hamcrest.OptionalMatchers.isEmpty;
18+
import static org.elasticsearch.test.hamcrest.OptionalMatchers.isPresentWith;
19+
import static org.hamcrest.Matchers.is;
20+
import static org.hamcrest.Matchers.sameInstance;
21+
22+
public class ResultTests extends ESTestCase {
23+
24+
public void testSuccess() {
25+
final String str = randomAlphaOfLengthBetween(3, 8);
26+
final Result<String, ElasticsearchException> result = Result.of(str);
27+
assertThat(result.isSuccessful(), is(true));
28+
assertThat(result.isFailure(), is(false));
29+
assertThat(result.get(), sameInstance(str));
30+
assertThat(result.failure(), isEmpty());
31+
assertThat(result.asOptional(), isPresentWith(str));
32+
}
33+
34+
public void testFailure() {
35+
final ElasticsearchException exception = new ElasticsearchStatusException(
36+
randomAlphaOfLengthBetween(10, 30),
37+
RestStatus.INTERNAL_SERVER_ERROR
38+
);
39+
final Result<String, ElasticsearchException> result = Result.failure(exception);
40+
assertThat(result.isSuccessful(), is(false));
41+
assertThat(result.isFailure(), is(true));
42+
assertThat(expectThrows(Exception.class, result::get), sameInstance(exception));
43+
assertThat(result.failure(), isPresentWith(sameInstance(exception)));
44+
assertThat(result.asOptional(), isEmpty());
45+
}
46+
47+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Hash.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1212
import org.elasticsearch.common.io.stream.StreamInput;
1313
import org.elasticsearch.common.io.stream.StreamOutput;
14+
import org.elasticsearch.common.util.Result;
1415
import org.elasticsearch.compute.ann.Evaluator;
1516
import org.elasticsearch.compute.ann.Fixed;
1617
import org.elasticsearch.compute.operator.BreakingBytesRefBuilder;
@@ -202,6 +203,14 @@ public static HashFunction create(BytesRef literal) throws NoSuchAlgorithmExcept
202203
return new HashFunction(algorithm, MessageDigest.getInstance(algorithm));
203204
}
204205

206+
public static Result<HashFunction, NoSuchAlgorithmException> tryCreate(String algorithm) {
207+
try {
208+
return Result.of(new HashFunction(algorithm, MessageDigest.getInstance(algorithm)));
209+
} catch (NoSuchAlgorithmException e) {
210+
return Result.failure(e);
211+
}
212+
}
213+
205214
public HashFunction copy() {
206215
try {
207216
return new HashFunction(algorithm, MessageDigest.getInstance(algorithm));

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Md5.java

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1111
import org.elasticsearch.common.io.stream.StreamInput;
12+
import org.elasticsearch.common.util.Result;
1213
import org.elasticsearch.xpack.esql.VerificationException;
1314
import org.elasticsearch.xpack.esql.core.expression.Expression;
1415
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
@@ -22,17 +23,21 @@
2223
import java.security.MessageDigest;
2324
import java.security.NoSuchAlgorithmException;
2425
import java.util.List;
25-
import java.util.concurrent.atomic.AtomicReference;
2626

2727
public class Md5 extends AbstractHashFunction {
2828

2929
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MD5", Md5::new);
3030

31-
private final AtomicReference<HashFunction> MD5 = new AtomicReference<>();
31+
/**
32+
* As of Java 14, it is permissible for a JRE to ship without the {@code MD5} {@link MessageDigest}.
33+
* We want the "md5" function in ES|QL to fail at runtime on such platforms (rather than at startup)
34+
* so we wrap the {@link HashFunction} in a {@link Result}.
35+
*/
36+
private static final Result<HashFunction, NoSuchAlgorithmException> MD5 = HashFunction.tryCreate("MD5");
3237

3338
@FunctionInfo(
3439
returnType = "keyword",
35-
description = "Computes the MD5 hash of the input.",
40+
description = "Computes the MD5 hash of the input (if the MD5 hash is available on the JVM).",
3641
examples = { @Example(file = "hash", tag = "md5") }
3742
)
3843
public Md5(Source source, @Param(name = "input", type = { "keyword", "text" }, description = "Input to hash.") Expression input) {
@@ -43,23 +48,14 @@ private Md5(StreamInput in) throws IOException {
4348
super(in);
4449
}
4550

46-
/**
47-
* As of Java 14, it is permissible for a JRE to ship without the {@code MD5} {@link MessageDigest}.
48-
* We want the "md5" function in ES|QL to fail at runtime on such platforms (rather than at startup)
49-
* so we build the {@link HashFunction} lazily.
50-
*/
5151
@Override
5252
protected HashFunction getHashFunction() {
53-
HashFunction function = MD5.get();
54-
if (function == null) {
55-
try {
56-
function = new HashFunction("MD5", MessageDigest.getInstance("MD5"));
57-
MD5.compareAndSet(null, function);
58-
} catch (NoSuchAlgorithmException e) {
59-
throw new VerificationException("function 'md5' is not available on this platform: {}", e.getMessage());
60-
}
53+
try {
54+
return MD5.get();
55+
} catch (NoSuchAlgorithmException e) {
56+
// Throw a new exception so that the stack trace reflects this call (rather than the static initializer for the MD5 field)
57+
throw new VerificationException("function 'md5' is not available on this platform: {}", e.getMessage());
6158
}
62-
return function;
6359
}
6460

6561
@Override

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/HashStaticTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,30 @@
1313
import org.elasticsearch.common.util.BigArrays;
1414
import org.elasticsearch.common.util.MockBigArrays;
1515
import org.elasticsearch.common.util.PageCacheRecycler;
16+
import org.elasticsearch.common.util.Result;
1617
import org.elasticsearch.compute.data.BlockFactory;
1718
import org.elasticsearch.compute.operator.DriverContext;
1819
import org.elasticsearch.test.ESTestCase;
20+
import org.elasticsearch.test.hamcrest.OptionalMatchers;
1921
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
2022
import org.elasticsearch.xpack.esql.core.expression.Literal;
2123
import org.elasticsearch.xpack.esql.core.tree.Source;
2224
import org.elasticsearch.xpack.esql.core.type.DataType;
2325
import org.junit.After;
2426

27+
import java.security.NoSuchAlgorithmException;
28+
import java.security.Provider;
29+
import java.security.Security;
2530
import java.util.ArrayList;
2631
import java.util.Collections;
2732
import java.util.List;
2833

34+
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
2935
import static org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase.evaluator;
3036
import static org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase.field;
37+
import static org.hamcrest.Matchers.containsString;
3138
import static org.hamcrest.Matchers.equalTo;
39+
import static org.hamcrest.Matchers.is;
3240
import static org.hamcrest.Matchers.startsWith;
3341

3442
public class HashStaticTests extends ESTestCase {
@@ -45,6 +53,27 @@ public void testInvalidAlgorithmLiteral() {
4553
assertThat(e.getMessage(), startsWith("invalid algorithm for [hast(\"invalid\", input)]: invalid MessageDigest not available"));
4654
}
4755

56+
public void testTryCreateUnavailableMd5() throws NoSuchAlgorithmException {
57+
assumeFalse("We run with different security providers in FIPS, and changing them at runtime is more complicated", inFipsJvm());
58+
final Provider sunProvider = Security.getProvider("SUN");
59+
try {
60+
Security.removeProvider("SUN");
61+
final Result<Hash.HashFunction, NoSuchAlgorithmException> result = Hash.HashFunction.tryCreate("MD5");
62+
assertThat(result.isSuccessful(), is(false));
63+
assertThat(result.failure(), OptionalMatchers.isPresentWith(throwableWithMessage(containsString("MD5"))));
64+
expectThrows(NoSuchAlgorithmException.class, result::get);
65+
} finally {
66+
Security.addProvider(sunProvider);
67+
}
68+
69+
{
70+
final Result<Hash.HashFunction, NoSuchAlgorithmException> result = Hash.HashFunction.tryCreate("MD5");
71+
assertThat(result.isSuccessful(), is(true));
72+
assertThat(result.failure(), OptionalMatchers.isEmpty());
73+
assertThat(result.get().algorithm(), is("MD5"));
74+
}
75+
}
76+
4877
/**
4978
* The following fields and methods were borrowed from AbstractScalarFunctionTestCase
5079
*/

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/Md5UnavailableTests.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)