Skip to content

Commit 6b2167f

Browse files
authored
Begin moving XContent to a separate lib/artifact (#29300)
* Begin moving XContent to a separate lib/artifact This commit moves a large portion of the XContent code from the `server` project to the `libs/xcontent` project. For the pieces that have been moved, some helpers have been duplicated to allow them to be decoupled from ES helper classes. In addition, `Booleans` and `CheckedFunction` have been moved to the `elasticsearch-core` project. This decoupling is a move so that we can eventually make things like the high-level REST client not rely on the entire ES jar, only the parts it needs. There are some pieces that are still not decoupled, in particular some of the XContent tests still remain in the server project, this is because they test a large portion of the pluggable xcontent pieces through `XContentElasticsearchException`. They may be decoupled in future work. Additionally, there may be more piecese that we want to move to the xcontent lib in the future that are not part of this PR, this is a starting point. Relates to #28504
1 parent 1172b3b commit 6b2167f

File tree

56 files changed

+395
-176
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+395
-176
lines changed

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ subprojects {
196196
"org.elasticsearch:elasticsearch-cli:${version}": ':server:cli',
197197
"org.elasticsearch:elasticsearch-core:${version}": ':libs:elasticsearch-core',
198198
"org.elasticsearch:elasticsearch-nio:${version}": ':libs:elasticsearch-nio',
199+
"org.elasticsearch:elasticsearch-x-content:${version}": ':libs:x-content',
199200
"org.elasticsearch:elasticsearch-secure-sm:${version}": ':libs:secure-sm',
200201
"org.elasticsearch.client:elasticsearch-rest-client:${version}": ':client:rest',
201202
"org.elasticsearch.client:elasticsearch-rest-client-sniffer:${version}": ':client:sniffer',

buildSrc/src/main/resources/checkstyle_suppressions.xml

-2
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,7 @@
248248
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]util[/\\]concurrent[/\\]EsExecutors.java" checks="LineLength" />
249249
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]util[/\\]concurrent[/\\]ThreadBarrier.java" checks="LineLength" />
250250
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]util[/\\]concurrent[/\\]ThreadContext.java" checks="LineLength" />
251-
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]xcontent[/\\]XContentFactory.java" checks="LineLength" />
252251
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]xcontent[/\\]XContentHelper.java" checks="LineLength" />
253-
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]xcontent[/\\]smile[/\\]SmileXContent.java" checks="LineLength" />
254252
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]discovery[/\\]Discovery.java" checks="LineLength" />
255253
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]discovery[/\\]DiscoverySettings.java" checks="LineLength" />
256254
<suppress files="server[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]discovery[/\\]zen[/\\]ZenDiscovery.java" checks="LineLength" />

server/src/main/java/org/elasticsearch/common/Booleans.java renamed to libs/elasticsearch-core/src/main/java/org/elasticsearch/common/Booleans.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,34 @@ public static boolean parseBoolean(String value) {
7373
throw new IllegalArgumentException("Failed to parse value [" + value + "] as only [true] or [false] are allowed.");
7474
}
7575

76+
private static boolean hasText(CharSequence str) {
77+
if (str == null || str.length() == 0) {
78+
return false;
79+
}
80+
int strLen = str.length();
81+
for (int i = 0; i < strLen; i++) {
82+
if (!Character.isWhitespace(str.charAt(i))) {
83+
return true;
84+
}
85+
}
86+
return false;
87+
}
88+
7689
/**
7790
*
7891
* @param value text to parse.
7992
* @param defaultValue The default value to return if the provided value is <code>null</code>.
8093
* @return see {@link #parseBoolean(String)}
8194
*/
8295
public static boolean parseBoolean(String value, boolean defaultValue) {
83-
if (Strings.hasText(value)) {
96+
if (hasText(value)) {
8497
return parseBoolean(value);
8598
}
8699
return defaultValue;
87100
}
88101

89102
public static Boolean parseBoolean(String value, Boolean defaultValue) {
90-
if (Strings.hasText(value)) {
103+
if (hasText(value)) {
91104
return parseBoolean(value);
92105
}
93106
return defaultValue;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common;
21+
22+
/**
23+
* Utility class for glob-like matching
24+
*/
25+
public class Glob {
26+
27+
/**
28+
* Match a String against the given pattern, supporting the following simple
29+
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
30+
* arbitrary number of pattern parts), as well as direct equality.
31+
*
32+
* @param pattern the pattern to match against
33+
* @param str the String to match
34+
* @return whether the String matches the given pattern
35+
*/
36+
public static boolean globMatch(String pattern, String str) {
37+
if (pattern == null || str == null) {
38+
return false;
39+
}
40+
int firstIndex = pattern.indexOf('*');
41+
if (firstIndex == -1) {
42+
return pattern.equals(str);
43+
}
44+
if (firstIndex == 0) {
45+
if (pattern.length() == 1) {
46+
return true;
47+
}
48+
int nextIndex = pattern.indexOf('*', firstIndex + 1);
49+
if (nextIndex == -1) {
50+
return str.endsWith(pattern.substring(1));
51+
} else if (nextIndex == 1) {
52+
// Double wildcard "**" - skipping the first "*"
53+
return globMatch(pattern.substring(1), str);
54+
}
55+
String part = pattern.substring(1, nextIndex);
56+
int partIndex = str.indexOf(part);
57+
while (partIndex != -1) {
58+
if (globMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
59+
return true;
60+
}
61+
partIndex = str.indexOf(part, partIndex + 1);
62+
}
63+
return false;
64+
}
65+
return (str.length() >= firstIndex &&
66+
pattern.substring(0, firstIndex).equals(str.substring(0, firstIndex)) &&
67+
globMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
68+
}
69+
70+
}

libs/x-content/build.gradle

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import org.elasticsearch.gradle.precommit.PrecommitTasks
2+
3+
/*
4+
* Licensed to Elasticsearch under one or more contributor
5+
* license agreements. See the NOTICE file distributed with
6+
* this work for additional information regarding copyright
7+
* ownership. Elasticsearch licenses this file to you under
8+
* the Apache License, Version 2.0 (the "License"); you may
9+
* not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
apply plugin: 'elasticsearch.build'
23+
apply plugin: 'nebula.maven-base-publish'
24+
apply plugin: 'nebula.maven-scm'
25+
26+
archivesBaseName = 'elasticsearch-x-content'
27+
28+
publishing {
29+
publications {
30+
nebula {
31+
artifactId = archivesBaseName
32+
}
33+
}
34+
}
35+
36+
dependencies {
37+
compile "org.elasticsearch:elasticsearch-core:${version}"
38+
39+
compile "org.yaml:snakeyaml:${versions.snakeyaml}"
40+
compile "com.fasterxml.jackson.core:jackson-core:${versions.jackson}"
41+
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-smile:${versions.jackson}"
42+
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
43+
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${versions.jackson}"
44+
45+
testCompile "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
46+
testCompile "junit:junit:${versions.junit}"
47+
testCompile "org.hamcrest:hamcrest-all:${versions.hamcrest}"
48+
49+
if (isEclipse == false || project.path == ":libs:x-content-tests") {
50+
testCompile("org.elasticsearch.test:framework:${version}") {
51+
exclude group: 'org.elasticsearch', module: 'elasticsearch-x-content'
52+
}
53+
}
54+
55+
}
56+
57+
forbiddenApisMain {
58+
// x-content does not depend on server
59+
// TODO: Need to decide how we want to handle for forbidden signatures with the changes to core
60+
signaturesURLs = [PrecommitTasks.getResource('/forbidden/jdk-signatures.txt')]
61+
}
62+
63+
if (isEclipse) {
64+
// in eclipse the project is under a fake root, we need to change around the source sets
65+
sourceSets {
66+
if (project.path == ":libs:x-content") {
67+
main.java.srcDirs = ['java']
68+
main.resources.srcDirs = ['resources']
69+
} else {
70+
test.java.srcDirs = ['java']
71+
test.resources.srcDirs = ['resources']
72+
}
73+
}
74+
}
75+
76+
thirdPartyAudit.excludes = [
77+
// from com.fasterxml.jackson.dataformat.yaml.YAMLMapper (jackson-dataformat-yaml)
78+
'com.fasterxml.jackson.databind.ObjectMapper',
79+
]
80+
81+
dependencyLicenses {
82+
mapping from: /jackson-.*/, to: 'jackson'
83+
}
84+
85+
jarHell.enabled = false

server/src/main/java/org/elasticsearch/common/ParseField.java renamed to libs/x-content/src/main/java/org/elasticsearch/common/ParseField.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class ParseField {
3535
private String allReplacedWith = null;
3636
private final String[] allNames;
3737

38+
private static final String[] EMPTY = new String[0];
39+
3840
/**
3941
* @param name
4042
* the primary name for this field. This will be returned by
@@ -46,7 +48,7 @@ public class ParseField {
4648
public ParseField(String name, String... deprecatedNames) {
4749
this.name = name;
4850
if (deprecatedNames == null || deprecatedNames.length == 0) {
49-
this.deprecatedNames = Strings.EMPTY_ARRAY;
51+
this.deprecatedNames = EMPTY;
5052
} else {
5153
final HashSet<String> set = new HashSet<>();
5254
Collections.addAll(set, deprecatedNames);

server/src/main/java/org/elasticsearch/common/xcontent/ToXContent.java renamed to libs/x-content/src/main/java/org/elasticsearch/common/xcontent/ToXContent.java

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
package org.elasticsearch.common.xcontent;
2121

22+
import org.elasticsearch.common.Booleans;
23+
2224
import java.io.IOException;
2325
import java.util.Map;
2426

server/src/main/java/org/elasticsearch/common/xcontent/XContent.java renamed to libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContent.java

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
package org.elasticsearch.common.xcontent;
2121

22+
import org.elasticsearch.common.Booleans;
23+
2224
import java.io.IOException;
2325
import java.io.InputStream;
2426
import java.io.OutputStream;

server/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java renamed to libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java

+41-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
package org.elasticsearch.common.xcontent;
2121

22-
import org.elasticsearch.common.util.CollectionUtils;
23-
2422
import java.io.ByteArrayOutputStream;
2523
import java.io.Closeable;
2624
import java.io.Flushable;
@@ -35,6 +33,7 @@
3533
import java.util.Date;
3634
import java.util.GregorianCalendar;
3735
import java.util.HashMap;
36+
import java.util.IdentityHashMap;
3837
import java.util.Locale;
3938
import java.util.Map;
4039
import java.util.Objects;
@@ -740,7 +739,9 @@ private void unknownValue(Object value, boolean ensureNoSelfReferences) throws I
740739
//Path implements Iterable<Path> and causes endless recursion and a StackOverFlow if treated as an Iterable here
741740
value((Path) value);
742741
} else if (value instanceof Map) {
743-
map((Map<String,?>) value, ensureNoSelfReferences);
742+
@SuppressWarnings("unchecked")
743+
final Map<String, ?> valueMap = (Map<String, ?>) value;
744+
map(valueMap, ensureNoSelfReferences);
744745
} else if (value instanceof Iterable) {
745746
value((Iterable<?>) value, ensureNoSelfReferences);
746747
} else if (value instanceof Object[]) {
@@ -799,7 +800,7 @@ private XContentBuilder map(Map<String, ?> values, boolean ensureNoSelfReference
799800
// checks that the map does not contain references to itself because
800801
// iterating over map entries will cause a stackoverflow error
801802
if (ensureNoSelfReferences) {
802-
CollectionUtils.ensureNoSelfReferences(values);
803+
ensureNoSelfReferences(values);
803804
}
804805

805806
startObject();
@@ -828,7 +829,7 @@ private XContentBuilder value(Iterable<?> values, boolean ensureNoSelfReferences
828829
// checks that the iterable does not contain references to itself because
829830
// iterating over entries will cause a stackoverflow error
830831
if (ensureNoSelfReferences) {
831-
CollectionUtils.ensureNoSelfReferences(values);
832+
ensureNoSelfReferences(values);
832833
}
833834
startArray();
834835
for (Object value : values) {
@@ -937,4 +938,39 @@ static void ensureNotNull(Object value, String message) {
937938
throw new IllegalArgumentException(message);
938939
}
939940
}
941+
942+
private static void ensureNoSelfReferences(Object value) {
943+
Iterable<?> it = convert(value);
944+
if (it != null) {
945+
ensureNoSelfReferences(it, value, Collections.newSetFromMap(new IdentityHashMap<>()));
946+
}
947+
}
948+
949+
private static Iterable<?> convert(Object value) {
950+
if (value == null) {
951+
return null;
952+
}
953+
if (value instanceof Map) {
954+
return ((Map<?,?>) value).values();
955+
} else if ((value instanceof Iterable) && (value instanceof Path == false)) {
956+
return (Iterable<?>) value;
957+
} else if (value instanceof Object[]) {
958+
return Arrays.asList((Object[]) value);
959+
} else {
960+
return null;
961+
}
962+
}
963+
964+
private static void ensureNoSelfReferences(final Iterable<?> value, Object originalReference, final Set<Object> ancestors) {
965+
if (value != null) {
966+
if (ancestors.add(originalReference) == false) {
967+
throw new IllegalArgumentException("Iterable object is self-referencing itself");
968+
}
969+
for (Object o : value) {
970+
ensureNoSelfReferences(convert(o), o, ancestors);
971+
}
972+
ancestors.remove(originalReference);
973+
}
974+
}
975+
940976
}

server/src/main/java/org/elasticsearch/common/xcontent/XContentFactory.java renamed to libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentFactory.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import com.fasterxml.jackson.dataformat.cbor.CBORConstants;
2323
import com.fasterxml.jackson.dataformat.smile.SmileConstants;
24-
import org.elasticsearch.ElasticsearchParseException;
2524
import org.elasticsearch.common.xcontent.cbor.CborXContent;
2625
import org.elasticsearch.common.xcontent.json.JsonXContent;
2726
import org.elasticsearch.common.xcontent.smile.SmileXContent;
@@ -154,7 +153,8 @@ public static XContentType xContentType(CharSequence content) {
154153
return XContentType.JSON;
155154
}
156155
// Should we throw a failure here? Smile idea is to use it in bytes....
157-
if (length > 2 && first == SmileConstants.HEADER_BYTE_1 && content.charAt(1) == SmileConstants.HEADER_BYTE_2 && content.charAt(2) == SmileConstants.HEADER_BYTE_3) {
156+
if (length > 2 && first == SmileConstants.HEADER_BYTE_1 && content.charAt(1) == SmileConstants.HEADER_BYTE_2 &&
157+
content.charAt(2) == SmileConstants.HEADER_BYTE_3) {
158158
return XContentType.SMILE;
159159
}
160160
if (length > 2 && first == '-' && content.charAt(1) == '-' && content.charAt(2) == '-') {
@@ -186,7 +186,7 @@ public static XContentType xContentType(CharSequence content) {
186186
public static XContent xContent(CharSequence content) {
187187
XContentType type = xContentType(content);
188188
if (type == null) {
189-
throw new ElasticsearchParseException("Failed to derive xcontent");
189+
throw new XContentParseException("Failed to derive xcontent");
190190
}
191191
return xContent(type);
192192
}
@@ -213,7 +213,7 @@ public static XContent xContent(byte[] data) {
213213
public static XContent xContent(byte[] data, int offset, int length) {
214214
XContentType type = xContentType(data, offset, length);
215215
if (type == null) {
216-
throw new ElasticsearchParseException("Failed to derive xcontent");
216+
throw new XContentParseException("Failed to derive xcontent");
217217
}
218218
return xContent(type);
219219
}
@@ -278,7 +278,8 @@ public static XContentType xContentType(byte[] bytes, int offset, int length) {
278278
if (first == '{') {
279279
return XContentType.JSON;
280280
}
281-
if (length > 2 && first == SmileConstants.HEADER_BYTE_1 && bytes[offset + 1] == SmileConstants.HEADER_BYTE_2 && bytes[offset + 2] == SmileConstants.HEADER_BYTE_3) {
281+
if (length > 2 && first == SmileConstants.HEADER_BYTE_1 && bytes[offset + 1] == SmileConstants.HEADER_BYTE_2 &&
282+
bytes[offset + 2] == SmileConstants.HEADER_BYTE_3) {
282283
return XContentType.SMILE;
283284
}
284285
if (length > 2 && first == '-' && bytes[offset + 1] == '-' && bytes[offset + 2] == '-') {

0 commit comments

Comments
 (0)