Skip to content

Commit 29ecd48

Browse files
mp911desothawo
authored andcommitted
DATAES-628 - Introduce Document API.
We now expose a Document API to encapsulate data interchanged with Elasticsearch (get responses, search hits, arbitrary maps) for a consistent API.
1 parent ce686b1 commit 29ecd48

File tree

6 files changed

+1739
-0
lines changed

6 files changed

+1739
-0
lines changed
Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch;
17+
18+
import java.io.IOException;
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
import java.util.function.BooleanSupplier;
22+
import java.util.function.Function;
23+
import java.util.function.IntSupplier;
24+
import java.util.function.LongSupplier;
25+
import java.util.function.Supplier;
26+
27+
import org.springframework.lang.Nullable;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* A representation of a Elasticsearch document as extended {@link Map} with {@link String} keys. All iterators preserve
32+
* original insertion order.
33+
* <p>
34+
* Document does not allow {@code null} keys. It allows {@literal null} values.
35+
* <p>
36+
* Implementing classes can bei either mutable or immutable. In case a subclass is immutable, its methods may throw
37+
* {@link UnsupportedOperationException} when calling modifying methods.
38+
*
39+
* @author Mark Paluch
40+
* @since 4.0
41+
*/
42+
public interface Document extends Map<String, Object> {
43+
44+
/**
45+
* Create a new mutable {@link Document}.
46+
*
47+
* @return a new {@link Document}.
48+
*/
49+
static Document create() {
50+
return new MapDocument();
51+
}
52+
53+
/**
54+
* Create a {@link Document} from a {@link Map} containing key-value pairs and sub-documents.
55+
*
56+
* @param map source map containing key-value pairs and sub-documents.
57+
* @return a new {@link Document}.
58+
*/
59+
static Document from(Map<String, Object> map) {
60+
61+
Assert.notNull(map, "Map must not be null");
62+
63+
if (map instanceof LinkedHashMap) {
64+
return new MapDocument(map);
65+
}
66+
67+
return new MapDocument(new LinkedHashMap<>(map));
68+
}
69+
70+
/**
71+
* Parse JSON to {@link Document}.
72+
*
73+
* @param json must not be {@literal null}.
74+
* @return the parsed {@link Document}.
75+
*/
76+
static Document parse(String json) {
77+
try {
78+
return new MapDocument(MapDocument.OBJECT_MAPPER.readerFor(Map.class).readValue(json));
79+
} catch (IOException e) {
80+
throw new ElasticsearchException("Cannot parse JSON", e);
81+
}
82+
}
83+
84+
/**
85+
* {@link #put(Object, Object)} the {@code key}/{@code value} tuple and return {@code this} {@link Document}.
86+
*
87+
* @param key key with which the specified value is to be associated.
88+
* @param value value to be associated with the specified key.
89+
* @return {@code this} {@link Document}.
90+
*/
91+
default Document append(String key, Object value) {
92+
put(key, value);
93+
return this;
94+
}
95+
96+
/**
97+
* Return {@literal true} if this {@link Document} is associated with an identifier.
98+
*
99+
* @return {@literal true} if this {@link Document} is associated with an identifier, {@literal false} otherwise.
100+
*/
101+
default boolean hasId() {
102+
return false;
103+
}
104+
105+
/**
106+
* Retrieve the identifier associated with this {@link Document}.
107+
* <p>
108+
* The default implementation throws {@link UnsupportedOperationException}. It's recommended to check {@link #hasId()}
109+
* prior to calling this method.
110+
*
111+
* @return the identifier associated with this {@link Document}.
112+
* @throws IllegalStateException if the underlying implementation supports Id's but no Id was yet associated with the
113+
* document.
114+
*/
115+
default String getId() {
116+
throw new UnsupportedOperationException();
117+
}
118+
119+
/**
120+
* Set the identifier for this {@link Document}.
121+
* <p>
122+
* The default implementation throws {@link UnsupportedOperationException}.
123+
*/
124+
default void setId(String id) {
125+
throw new UnsupportedOperationException();
126+
}
127+
128+
/**
129+
* Return {@literal true} if this {@link Document} is associated with a version.
130+
*
131+
* @return {@literal true} if this {@link Document} is associated with a version, {@literal false} otherwise.
132+
*/
133+
default boolean hasVersion() {
134+
return false;
135+
}
136+
137+
/**
138+
* Retrieve the version associated with this {@link Document}.
139+
* <p>
140+
* The default implementation throws {@link UnsupportedOperationException}. It's recommended to check
141+
* {@link #hasVersion()} prior to calling this method.
142+
*
143+
* @return the version associated with this {@link Document}.
144+
* @throws IllegalStateException if the underlying implementation supports Id's but no Id was yet associated with the
145+
* document.
146+
*/
147+
default long getVersion() {
148+
throw new UnsupportedOperationException();
149+
}
150+
151+
/**
152+
* Set the version for this {@link Document}.
153+
* <p>
154+
* The default implementation throws {@link UnsupportedOperationException}.
155+
*/
156+
default void setVersion(long version) {
157+
throw new UnsupportedOperationException();
158+
}
159+
160+
/**
161+
* Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no
162+
* mapping for the key. The value is casted within the method which makes it useful for calling code as it does not
163+
* require casting on the calling side. If the value type is not assignable to {@code type}, then this method throws
164+
* {@link ClassCastException}.
165+
*
166+
* @param key the key whose associated value is to be returned
167+
* @param type the expected return value type.
168+
* @param <T> expected return type.
169+
* @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for
170+
* the key.
171+
* @throws ClassCastException if the value of the given key is not of {@code type T}.
172+
*/
173+
@Nullable
174+
default <T> T get(Object key, Class<T> type) {
175+
176+
Assert.notNull(key, "Key must not be null");
177+
Assert.notNull(type, "Type must not be null");
178+
179+
return type.cast(get(key));
180+
}
181+
182+
/**
183+
* Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no
184+
* mapping for the key. If the value type is not a {@link Boolean}, then this method throws
185+
* {@link ClassCastException}.
186+
*
187+
* @param key the key whose associated value is to be returned
188+
* @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for
189+
* the key.
190+
* @throws ClassCastException if the value of the given key is not a {@link Boolean}.
191+
*/
192+
@Nullable
193+
default Boolean getBoolean(String key) {
194+
return get(key, Boolean.class);
195+
}
196+
197+
/**
198+
* Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no
199+
* mapping for the key. If the value type is not a {@link Boolean}, then this method throws
200+
* {@link ClassCastException}.
201+
*
202+
* @param key the key whose associated value is to be returned
203+
* @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping
204+
* for the key.
205+
* @throws ClassCastException if the value of the given key is not a {@link Boolean}.
206+
*/
207+
default boolean getBooleanOrDefault(String key, boolean defaultValue) {
208+
return getBooleanOrDefault(key, () -> defaultValue);
209+
}
210+
211+
/**
212+
* Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this
213+
* document contains no mapping for the key. If the value type is not a {@link Boolean}, then this method throws
214+
* {@link ClassCastException}.
215+
*
216+
* @param key the key whose associated value is to be returned
217+
* @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document
218+
* contains no mapping for the key.
219+
* @throws ClassCastException if the value of the given key is not a {@link Boolean}.
220+
* @see BooleanSupplier
221+
*/
222+
default boolean getBooleanOrDefault(String key, BooleanSupplier defaultValue) {
223+
224+
Boolean value = getBoolean(key);
225+
226+
return value == null ? defaultValue.getAsBoolean() : value;
227+
}
228+
229+
/**
230+
* Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no
231+
* mapping for the key. If the value type is not a {@link Integer}, then this method throws
232+
* {@link ClassCastException}.
233+
*
234+
* @param key the key whose associated value is to be returned
235+
* @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for
236+
* the key.
237+
* @throws ClassCastException if the value of the given key is not a {@link Integer}.
238+
*/
239+
@Nullable
240+
default Integer getInt(String key) {
241+
return get(key, Integer.class);
242+
}
243+
244+
/**
245+
* Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no
246+
* mapping for the key. If the value type is not a {@link Integer}, then this method throws
247+
* {@link ClassCastException}.
248+
*
249+
* @param key the key whose associated value is to be returned
250+
* @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping
251+
* for the key.
252+
* @throws ClassCastException if the value of the given key is not a {@link Integer}.
253+
*/
254+
default int getIntOrDefault(String key, int defaultValue) {
255+
return getIntOrDefault(key, () -> defaultValue);
256+
}
257+
258+
/**
259+
* Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this
260+
* document contains no mapping for the key. If the value type is not a {@link Integer}, then this method throws
261+
* {@link ClassCastException}.
262+
*
263+
* @param key the key whose associated value is to be returned
264+
* @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document
265+
* contains no mapping for the key.
266+
* @throws ClassCastException if the value of the given key is not a {@link Integer}.
267+
* @see IntSupplier
268+
*/
269+
default int getIntOrDefault(String key, IntSupplier defaultValue) {
270+
271+
Integer value = getInt(key);
272+
273+
return value == null ? defaultValue.getAsInt() : value;
274+
}
275+
276+
/**
277+
* Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no
278+
* mapping for the key. If the value type is not a {@link Long}, then this method throws {@link ClassCastException}.
279+
*
280+
* @param key the key whose associated value is to be returned
281+
* @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for
282+
* the key.
283+
* @throws ClassCastException if the value of the given key is not a {@link Long}.
284+
*/
285+
@Nullable
286+
default Long getLong(String key) {
287+
return get(key, Long.class);
288+
}
289+
290+
/**
291+
* Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no
292+
* mapping for the key. If the value type is not a {@link Long}, then this method throws {@link ClassCastException}.
293+
*
294+
* @param key the key whose associated value is to be returned
295+
* @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping
296+
* for the key.
297+
* @throws ClassCastException if the value of the given key is not a {@link Long}.
298+
*/
299+
default long getLongOrDefault(String key, long defaultValue) {
300+
return getLongOrDefault(key, () -> defaultValue);
301+
}
302+
303+
/**
304+
* Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this
305+
* document contains no mapping for the key. If the value type is not a {@link Long}, then this method throws
306+
* {@link ClassCastException}.
307+
*
308+
* @param key the key whose associated value is to be returned
309+
* @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document
310+
* contains no mapping for the key.
311+
* @throws ClassCastException if the value of the given key is not a {@link Long}.
312+
* @see LongSupplier
313+
*/
314+
default long getLongOrDefault(String key, LongSupplier defaultValue) {
315+
316+
Long value = getLong(key);
317+
318+
return value == null ? defaultValue.getAsLong() : value;
319+
}
320+
321+
/**
322+
* Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no
323+
* mapping for the key. If the value type is not a {@link String}, then this method throws {@link ClassCastException}.
324+
*
325+
* @param key the key whose associated value is to be returned
326+
* @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for
327+
* the key.
328+
* @throws ClassCastException if the value of the given key is not a {@link String}.
329+
*/
330+
@Nullable
331+
default String getString(String key) {
332+
return get(key, String.class);
333+
}
334+
335+
/**
336+
* Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no
337+
* mapping for the key. If the value type is not a {@link String}, then this method throws {@link ClassCastException}.
338+
*
339+
* @param key the key whose associated value is to be returned
340+
* @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping
341+
* for the key.
342+
* @throws ClassCastException if the value of the given key is not a {@link String}.
343+
*/
344+
default String getStringOrDefault(String key, String defaultValue) {
345+
return getStringOrDefault(key, () -> defaultValue);
346+
}
347+
348+
/**
349+
* Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this
350+
* document contains no mapping for the key. If the value type is not a {@link String}, then this method throws
351+
* {@link ClassCastException}.
352+
*
353+
* @param key the key whose associated value is to be returned
354+
* @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document
355+
* contains no mapping for the key.
356+
* @throws ClassCastException if the value of the given key is not a {@link String}.
357+
* @see Supplier
358+
*/
359+
default String getStringOrDefault(String key, Supplier<String> defaultValue) {
360+
361+
String value = getString(key);
362+
363+
return value == null ? defaultValue.get() : value;
364+
}
365+
366+
/**
367+
* This method allows the application of a function to {@code this} {@link Document}. The function should expect a
368+
* single {@link Document} argument and produce an {@code R} result.
369+
* <p>
370+
* Any exception thrown by the function will be propagated to the caller.
371+
*
372+
* @param transformer functional interface to a apply
373+
* @param <R> class of the result
374+
* @return the result of applying the function to this string
375+
* @see java.util.function.Function
376+
*/
377+
default <R> R transform(Function<? super Document, ? extends R> transformer) {
378+
return transformer.apply(this);
379+
}
380+
381+
/**
382+
* Render this {@link Document} to JSON. Auxiliary values such as Id and version are not considered within the JSON
383+
* representation.
384+
*
385+
* @return a JSON representation of this document.
386+
*/
387+
String toJson();
388+
}

0 commit comments

Comments
 (0)