1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.periscope.search.rest;
35
36 import static com.google.common.base.Charsets.UTF_8;
37
38 import info.magnolia.periscope.operation.request.ExternalNavigationRequest;
39 import info.magnolia.periscope.search.SearchException;
40 import info.magnolia.periscope.search.SearchQuery;
41 import info.magnolia.periscope.search.SearchResult;
42 import info.magnolia.periscope.search.SearchResultSupplier;
43
44 import java.io.UnsupportedEncodingException;
45 import java.net.URLEncoder;
46 import java.time.ZonedDateTime;
47 import java.time.format.DateTimeFormatter;
48 import java.time.format.DateTimeParseException;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Objects;
52 import java.util.StringJoiner;
53 import java.util.stream.Stream;
54
55 import javax.ws.rs.client.Client;
56 import javax.ws.rs.client.ClientBuilder;
57 import javax.ws.rs.client.WebTarget;
58 import javax.ws.rs.core.MediaType;
59
60 import org.apache.commons.collections4.CollectionUtils;
61 import org.apache.commons.lang3.StringUtils;
62 import org.jsoup.Jsoup;
63 import org.jsoup.parser.Parser;
64 import org.jsoup.safety.Whitelist;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 import com.jayway.jsonpath.JsonPath;
69 import com.jayway.jsonpath.ReadContext;
70
71
72
73
74
75
76
77
78
79
80
81 @Deprecated
82 public class RestSearchResultSupplier implements SearchResultSupplier {
83
84 private static final Logger log = LoggerFactory.getLogger(RestSearchResultSupplier.class);
85
86 private final String name;
87 private final RestSearchResultSupplierDefinition definition;
88 private final String baseUrl;
89 private final String requestParameters;
90 private final Client client;
91
92 RestSearchResultSupplier(String name, RestSearchResultSupplierDefinition definition) {
93 this.name = name;
94 this.definition = definition;
95 this.baseUrl = definition.getBaseUrl();
96
97
98
99 this.requestParameters = definition.getRequestParameters() != null ?
100 Parser.unescapeEntities(Jsoup.clean(definition.getRequestParameters(), Whitelist.basic()), false) :
101 "";
102
103 Client restClient;
104 try {
105 restClient = ClientBuilder.newClient();
106 } catch (RuntimeException e) {
107 log.error("REST Client couldn't be built, is there any JAX-RS client impl on the classpath?", e);
108 restClient = null;
109 }
110 this.client = restClient;
111 }
112
113 @Override
114 public String getName() {
115 return this.name;
116 }
117
118 @Override
119 public Stream<SearchResult> search(SearchQuery query) throws SearchException {
120 sanityCheck();
121
122 if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(definition.getNavigationBaseURL())
123 || StringUtils.isBlank(definition.getTitleJsonPath()) || StringUtils.isBlank(definition.getNavigationURLJsonPath())) {
124 throw new SearchException(query.getQuery(),
125 new IllegalArgumentException("RestSearchResultSupplierDefinition is not configured correctly."));
126 }
127
128 try {
129 String targetUrl = constructTargetUrl(query);
130 WebTarget target = client.target(targetUrl);
131 String resultJsonString = target.request(MediaType.APPLICATION_JSON_TYPE).get(String.class);
132
133 return createResults(resultJsonString);
134 } catch (UnsupportedEncodingException e) {
135 throw new SearchException(query.getQuery(), e);
136 }
137 }
138
139
140
141
142
143
144 private void sanityCheck() {
145 if (baseUrl.isEmpty()) {
146 throw new IllegalStateException(String.format("baseUrl is empty, check logs for '%s' resultSupplier initialization problems.", this.name));
147 } else if (client == null) {
148 throw new IllegalStateException(String.format("REST Client could not be initialized properly, check logs for '%s' resultSupplier initialization problems.", this.name));
149 }
150 }
151
152 protected String constructTargetUrl(SearchQuery query) throws UnsupportedEncodingException {
153 String targetUrl = baseUrl + requestParameters;
154 targetUrl = StringUtils.replace(targetUrl, "${query}", URLEncoder.encode(query.getQuery(), UTF_8.name()));
155 targetUrl = StringUtils.replace(targetUrl, "${editorClause}", constructEditorClause(query));
156 targetUrl = StringUtils.replace(targetUrl, "${dateClause}", constructDateClause(query));
157 return targetUrl;
158 }
159
160 protected String constructEditorClause(SearchQuery query) {
161 if (CollectionUtils.isNotEmpty(query.getEditors())) {
162 StringJoiner clauses = new StringJoiner(" OR ");
163 query.getEditors().forEach(editor -> clauses.add(definition.getEditorField() + " = '" + editor + "'"));
164 return " AND (" + clauses + ")";
165 }
166 return StringUtils.EMPTY;
167 }
168
169 protected String constructDateClause(SearchQuery query) {
170 if (query.getStartDate() != null || query.getEndDate() != null) {
171 StringJoiner clauses = new StringJoiner(" AND ");
172 if (query.getStartDate() != null) {
173 clauses.add(definition.getDateField() + " >= " + query.getStartDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
174 }
175 if (query.getEndDate() != null) {
176 clauses.add(definition.getDateField() + " <= " + query.getEndDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
177 }
178 return " AND (" + clauses + ")";
179 }
180 return StringUtils.EMPTY;
181 }
182
183 private Stream<SearchResult> createResults(String resultJson) {
184 ReadContext readContext = JsonPath.parse(resultJson);
185 List<String> titles = readContext.read(definition.getTitleJsonPath());
186 List<String> urls = readContext.read(definition.getNavigationURLJsonPath());
187 List<String> dates = Collections.emptyList();
188 List<String> editors = Collections.emptyList();
189
190 if (StringUtils.isNotBlank(definition.getDateJsonPath())) {
191 dates = readContext.read(definition.getDateJsonPath());
192 }
193
194 if (StringUtils.isNotBlank(definition.getEditorJsonPath())) {
195 editors = readContext.read(definition.getEditorJsonPath());
196 }
197
198 Stream.Builder<SearchResult> results = Stream.builder();
199
200
201
202
203
204 final int dataSetLength = titles.size();
205 if (dataSetLength != urls.size() ||
206 (dataSetLength != dates.size() && dates.size() > 0) ||
207 (dataSetLength != editors.size() && editors.size() > 0)) {
208 log.debug("There is a mismatch between titles, urls, dates and editors in the data set.");
209 log.debug("Either something is wrong with your JsonPath configuration, or the data is inconsistent.");
210 return results.build();
211 }
212
213 for (int i = 0; i < titles.size(); i++) {
214 final String url = formatOrAppend(String.valueOf(urls.get(i)));
215 results.add(SearchResult.builder()
216 .title(String.valueOf(titles.get(i)))
217 .operationRequest(new ExternalNavigationRequest(url))
218 .type("external-webpage")
219 .lastModified(dates.stream().filter(Objects::nonNull).skip(i).findFirst().map(RestSearchResultSupplier::parseToZonedDateTimeOrNull).orElse(null))
220 .lastModifiedBy(editors.stream().filter(Objects::nonNull).skip(i).findFirst().orElse(""))
221 .build()
222 );
223 }
224 return results.build();
225 }
226
227 private static ZonedDateTime parseToZonedDateTimeOrNull(String dateString) {
228 try {
229 return ZonedDateTime.parse(dateString);
230 } catch (DateTimeParseException e) {
231 return null;
232 }
233 }
234
235
236
237
238
239
240 private String formatOrAppend(String url) {
241 if (definition.getNavigationBaseURL().contains("%")) {
242 return String.format(definition.getNavigationBaseURL(), String.valueOf(url));
243 } else {
244 return definition.getNavigationBaseURL() + url;
245 }
246 }
247 }