View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  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  package org.apache.syncope.sra;
20  
21  import java.time.ZonedDateTime;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.concurrent.atomic.AtomicInteger;
26  import java.util.function.Consumer;
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  import org.apache.commons.lang3.BooleanUtils;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
32  import org.apache.syncope.client.lib.SyncopeClient;
33  import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
34  import org.apache.syncope.common.keymaster.client.api.ServiceOps;
35  import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
36  import org.apache.syncope.common.lib.to.SRARouteTO;
37  import org.apache.syncope.common.lib.types.SRARouteFilter;
38  import org.apache.syncope.common.lib.types.SRARoutePredicate;
39  import org.apache.syncope.common.rest.api.service.SRARouteService;
40  import org.apache.syncope.sra.filters.ClientCertsToRequestHeaderFilterFactory;
41  import org.apache.syncope.sra.filters.CustomGatewayFilterFactory;
42  import org.apache.syncope.sra.filters.LinkRewriteGatewayFilterFactory;
43  import org.apache.syncope.sra.filters.PrincipalToRequestHeaderFilterFactory;
44  import org.apache.syncope.sra.filters.QueryParamToRequestHeaderFilterFactory;
45  import org.apache.syncope.sra.predicates.CustomRoutePredicateFactory;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  import org.springframework.cloud.gateway.filter.GatewayFilter;
49  import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
50  import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
51  import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
52  import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
53  import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory;
54  import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
55  import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory;
56  import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
57  import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
58  import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
59  import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
60  import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
61  import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
62  import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
63  import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory;
64  import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
65  import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory;
66  import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
67  import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory;
68  import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
69  import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
70  import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
71  import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
72  import org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory;
73  import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
74  import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
75  import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
76  import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
77  import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
78  import org.springframework.cloud.gateway.handler.AsyncPredicate;
79  import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
80  import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory;
81  import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
82  import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
83  import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory;
84  import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
85  import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory;
86  import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
87  import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory;
88  import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory;
89  import org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory;
90  import org.springframework.cloud.gateway.route.Route;
91  import org.springframework.context.ConfigurableApplicationContext;
92  import org.springframework.core.Ordered;
93  import org.springframework.http.HttpMethod;
94  import org.springframework.http.HttpStatus;
95  import org.springframework.util.unit.DataSize;
96  import org.springframework.web.server.ServerWebExchange;
97  
98  public class RouteProvider {
99  
100     protected static final Logger LOG = LoggerFactory.getLogger(RouteProvider.class);
101 
102     protected final ServiceOps serviceOps;
103 
104     protected final ConfigurableApplicationContext ctx;
105 
106     protected final String anonymousUser;
107 
108     protected final String anonymousKey;
109 
110     protected final boolean useGZIPCompression;
111 
112     protected SyncopeClient client;
113 
114     protected final List<SRARouteTO> routeTOs = new ArrayList<>();
115 
116     public RouteProvider(
117             final ServiceOps serviceOps,
118             final ConfigurableApplicationContext ctx,
119             final String anonymousUser,
120             final String anonymousKey,
121             final boolean useGZIPCompression) {
122 
123         this.serviceOps = serviceOps;
124         this.ctx = ctx;
125         this.anonymousUser = anonymousUser;
126         this.anonymousKey = anonymousKey;
127         this.useGZIPCompression = useGZIPCompression;
128     }
129 
130     @SuppressWarnings("unchecked")
131     protected GatewayFilter toFilter(final SRARouteTO route, final SRARouteFilter gwfilter)
132             throws ClassNotFoundException, NumberFormatException {
133 
134         GatewayFilter filter;
135 
136         switch (gwfilter.getFactory()) {
137             case ADD_REQUEST_HEADER:
138                 String[] addRequestHeaderArgs = gwfilter.getArgs().split(",");
139                 filter = ctx.getBean(AddRequestHeaderGatewayFilterFactory.class).
140                         apply(c -> c.setName(addRequestHeaderArgs[0].trim()).
141                         setValue(addRequestHeaderArgs[1].trim()));
142                 break;
143 
144             case ADD_REQUEST_PARAMETER:
145                 String[] addRequestParameterArgs = gwfilter.getArgs().split(",");
146                 filter = ctx.getBean(AddRequestParameterGatewayFilterFactory.class).
147                         apply(c -> c.setName(addRequestParameterArgs[0].trim()).
148                         setValue(addRequestParameterArgs[1].trim()));
149                 break;
150 
151             case ADD_RESPONSE_HEADER:
152                 String[] addResponseHeaderArgs = gwfilter.getArgs().split(",");
153                 filter = ctx.getBean(AddResponseHeaderGatewayFilterFactory.class).
154                         apply(c -> c.setName(addResponseHeaderArgs[0].trim()).
155                         setValue(addResponseHeaderArgs[1].trim()));
156                 break;
157 
158             case DEDUPE_RESPONSE_HEADER:
159                 String[] dedupeResponseHeaderArgs = gwfilter.getArgs().split(",");
160                 filter = ctx.getBean(DedupeResponseHeaderGatewayFilterFactory.class).
161                         apply(c -> {
162                             c.setName(dedupeResponseHeaderArgs[0].trim());
163                             if (dedupeResponseHeaderArgs.length > 1) {
164                                 c.setStrategy(DedupeResponseHeaderGatewayFilterFactory.Strategy.
165                                         valueOf(dedupeResponseHeaderArgs[1].trim()));
166                             }
167                         });
168                 break;
169 
170             case FALLBACK_HEADERS:
171                 String[] fallbackHeadersArgs = gwfilter.getArgs().split(",");
172                 filter = ctx.getBean(FallbackHeadersGatewayFilterFactory.class).
173                         apply(c -> {
174                             if (StringUtils.isNotBlank(fallbackHeadersArgs[0])) {
175                                 c.setExecutionExceptionTypeHeaderName(fallbackHeadersArgs[0].trim());
176                             }
177                             if (StringUtils.isNotBlank(fallbackHeadersArgs[1])) {
178                                 c.setExecutionExceptionMessageHeaderName(fallbackHeadersArgs[1].trim());
179                             }
180                             if (StringUtils.isNotBlank(fallbackHeadersArgs[2])) {
181                                 c.setRootCauseExceptionTypeHeaderName(fallbackHeadersArgs[2].trim());
182                             }
183                             if (StringUtils.isNotBlank(fallbackHeadersArgs[3])) {
184                                 c.setRootCauseExceptionMessageHeaderName(fallbackHeadersArgs[3].trim());
185                             }
186                         });
187                 break;
188 
189             case MAP_REQUEST_HEADER:
190                 String[] mapRequestHeaderArgs = gwfilter.getArgs().split(",");
191                 filter = ctx.getBean(MapRequestHeaderGatewayFilterFactory.class).
192                         apply(c -> c.setFromHeader(mapRequestHeaderArgs[0].trim()).
193                         setToHeader(mapRequestHeaderArgs[1].trim()));
194                 break;
195 
196             case PREFIX_PATH:
197                 filter = ctx.getBean(PrefixPathGatewayFilterFactory.class).
198                         apply(c -> c.setPrefix(gwfilter.getArgs().trim()));
199                 break;
200 
201             case PRESERVE_HOST_HEADER:
202                 filter = ctx.getBean(PreserveHostHeaderGatewayFilterFactory.class).apply();
203                 break;
204 
205             case REDIRECT_TO:
206                 String[] redirectArgs = gwfilter.getArgs().split(",");
207                 filter = ctx.getBean(RedirectToGatewayFilterFactory.class).
208                         apply(redirectArgs[0].trim(), redirectArgs[1].trim());
209                 break;
210 
211             case REMOVE_REQUEST_HEADER:
212                 filter = ctx.getBean(RemoveRequestHeaderGatewayFilterFactory.class).
213                         apply(c -> c.setName(gwfilter.getArgs().trim()));
214                 break;
215 
216             case REMOVE_RESPONSE_HEADER:
217                 filter = ctx.getBean(RemoveResponseHeaderGatewayFilterFactory.class).
218                         apply(c -> c.setName(gwfilter.getArgs().trim()));
219                 break;
220 
221             case REQUEST_RATE_LIMITER:
222                 String[] requestRateLimiterArgs = gwfilter.getArgs().split(",");
223                 filter = ctx.getBean(RequestRateLimiterGatewayFilterFactory.class).
224                         apply(c -> {
225                             if (StringUtils.isNotBlank(requestRateLimiterArgs[0])) {
226                                 c.setDenyEmptyKey(BooleanUtils.toBoolean(requestRateLimiterArgs[0].trim()));
227                             }
228                             if (StringUtils.isNotBlank(requestRateLimiterArgs[1])) {
229                                 c.setEmptyKeyStatus(requestRateLimiterArgs[1].trim());
230                             }
231                             if (StringUtils.isNotBlank(requestRateLimiterArgs[2])) {
232                                 c.setKeyResolver(ctx.getBean(requestRateLimiterArgs[2].trim(), KeyResolver.class));
233                             }
234                             if (StringUtils.isNotBlank(requestRateLimiterArgs[3])) {
235                                 c.setRateLimiter(ctx.getBean(requestRateLimiterArgs[3].trim(), RateLimiter.class));
236                             }
237                             if (StringUtils.isNotBlank(requestRateLimiterArgs[4])) {
238                                 c.setStatusCode(HttpStatus.valueOf(requestRateLimiterArgs[4].trim()));
239                             }
240                         });
241                 break;
242 
243             case REWRITE_PATH:
244                 String[] rewritePathArgs = gwfilter.getArgs().split(",");
245                 filter = ctx.getBean(RewritePathGatewayFilterFactory.class).
246                         apply(c -> c.setRegexp(rewritePathArgs[0].trim()).
247                         setReplacement(rewritePathArgs[1].trim()));
248                 break;
249 
250             case REWRITE_LOCATION:
251                 String[] rewriteLocationArgs = gwfilter.getArgs().split(",");
252                 filter = ctx.getBean(RewriteLocationResponseHeaderGatewayFilterFactory.class).
253                         apply(c -> {
254                             c.setStripVersion(RewriteLocationResponseHeaderGatewayFilterFactory.StripVersion.
255                                     valueOf(rewriteLocationArgs[0].trim()));
256                             if (rewriteLocationArgs.length > 1) {
257                                 c.setLocationHeaderName(rewriteLocationArgs[1].trim());
258                             }
259                             if (rewriteLocationArgs.length > 2) {
260                                 c.setHostValue(rewriteLocationArgs[2].trim());
261                             }
262                             if (rewriteLocationArgs.length > 3) {
263                                 c.setProtocols(rewriteLocationArgs[3].trim());
264                             }
265                         });
266                 break;
267 
268             case REWRITE_RESPONSE_HEADER:
269                 String[] rewriteResponseHeaderArgs = gwfilter.getArgs().split(",");
270                 filter = ctx.getBean(RewriteResponseHeaderGatewayFilterFactory.class).
271                         apply(c -> c.setReplacement(rewriteResponseHeaderArgs[2].trim()).
272                         setRegexp(rewriteResponseHeaderArgs[1].trim()).
273                         setName(rewriteResponseHeaderArgs[0].trim()));
274                 break;
275 
276             case RETRY:
277                 AtomicInteger retries = new AtomicInteger();
278                 try {
279                     retries.set(Integer.valueOf(gwfilter.getArgs().trim()));
280                 } catch (NumberFormatException e) {
281                     LOG.error("Unexpected argument value: {}", gwfilter.getArgs().trim(), e);
282                     retries.set(0);
283                 }
284                 filter = ctx.getBean(RetryGatewayFilterFactory.class).
285                         apply(c -> c.setRetries(retries.get()));
286                 break;
287 
288             case SAVE_SESSION:
289                 filter = ctx.getBean(SaveSessionGatewayFilterFactory.class).apply(c -> {
290                 });
291                 break;
292 
293             case SECURE_HEADERS:
294                 filter = ctx.getBean(SecureHeadersGatewayFilterFactory.class).apply(c -> {
295                 });
296                 break;
297 
298             case SET_PATH:
299                 filter = ctx.getBean(SetPathGatewayFilterFactory.class).
300                         apply(c -> c.setTemplate(gwfilter.getArgs().trim()));
301                 break;
302 
303             case SET_REQUEST_HEADER:
304                 String[] setRequestHeaderArgs = gwfilter.getArgs().split(",");
305                 filter = ctx.getBean(SetRequestHeaderGatewayFilterFactory.class).
306                         apply(c -> c.setName(setRequestHeaderArgs[0].trim()).
307                         setValue(setRequestHeaderArgs[1].trim()));
308                 break;
309 
310             case SET_RESPONSE_HEADER:
311                 String[] setResponseHeaderArgs = gwfilter.getArgs().split(",");
312                 filter = ctx.getBean(SetResponseHeaderGatewayFilterFactory.class).
313                         apply(c -> c.setName(setResponseHeaderArgs[0].trim()).
314                         setValue(setResponseHeaderArgs[1].trim()));
315                 break;
316 
317             case SET_STATUS:
318                 filter = ctx.getBean(SetStatusGatewayFilterFactory.class).
319                         apply(c -> c.setStatus(gwfilter.getArgs().trim()));
320                 break;
321 
322             case STRIP_PREFIX:
323                 AtomicInteger parts = new AtomicInteger();
324                 try {
325                     parts.set(Integer.valueOf(gwfilter.getArgs().trim()));
326                 } catch (NumberFormatException e) {
327                     LOG.error("Unexpected argument value: {}", gwfilter.getArgs().trim(), e);
328                     parts.set(0);
329                 }
330                 filter = ctx.getBean(StripPrefixGatewayFilterFactory.class).
331                         apply(c -> c.setParts(parts.get()));
332                 break;
333 
334             case REQUEST_HEADER_TO_REQUEST_URI:
335                 filter = ctx.getBean(RequestHeaderToRequestUriGatewayFilterFactory.class).
336                         apply(c -> c.setName(gwfilter.getArgs().trim()));
337                 break;
338 
339             case SET_REQUEST_SIZE:
340                 filter = ctx.getBean(RequestSizeGatewayFilterFactory.class).
341                         apply(c -> c.setMaxSize(DataSize.ofBytes(Long.valueOf(gwfilter.getArgs().trim()))));
342                 break;
343 
344             case SET_REQUEST_HOST:
345                 filter = ctx.getBean(SetRequestHostHeaderGatewayFilterFactory.class).
346                         apply(c -> c.setHost(gwfilter.getArgs().trim()));
347                 break;
348 
349             case LINK_REWRITE:
350                 filter = ApplicationContextUtils.getOrCreateBean(
351                         ctx,
352                         LinkRewriteGatewayFilterFactory.class.getName(),
353                         LinkRewriteGatewayFilterFactory.class).
354                         apply(c -> c.setData(route.getTarget().toASCIIString() + "," + gwfilter.getArgs().trim()));
355                 break;
356 
357             case CLIENT_CERTS_TO_REQUEST_HEADER:
358                 String header = StringUtils.isBlank(gwfilter.getArgs()) ? "X-Client-Certificate" : gwfilter.getArgs();
359                 filter = ApplicationContextUtils.getOrCreateBean(
360                         ctx,
361                         ClientCertsToRequestHeaderFilterFactory.class.getName(),
362                         ClientCertsToRequestHeaderFilterFactory.class).
363                         apply(c -> c.setName(header.trim()));
364                 break;
365 
366             case QUERY_PARAM_TO_REQUEST_HEADER:
367                 filter = ApplicationContextUtils.getOrCreateBean(
368                         ctx,
369                         QueryParamToRequestHeaderFilterFactory.class.getName(),
370                         QueryParamToRequestHeaderFilterFactory.class).
371                         apply(c -> c.setName(gwfilter.getArgs().trim()));
372                 break;
373 
374             case PRINCIPAL_TO_REQUEST_HEADER:
375                 filter = ApplicationContextUtils.getOrCreateBean(
376                         ctx,
377                         PrincipalToRequestHeaderFilterFactory.class.getName(),
378                         PrincipalToRequestHeaderFilterFactory.class).
379                         apply(c -> c.setName(gwfilter.getArgs().trim()));
380                 break;
381 
382             case CUSTOM:
383                 String[] customArgs = gwfilter.getArgs().split(";");
384                 Consumer<CustomGatewayFilterFactory.Config> customConsumer = customArgs.length > 1
385                         ? c -> c.setData(customArgs[1])
386                         : c -> c.setData(null);
387                 CustomGatewayFilterFactory factory = ApplicationContextUtils.getOrCreateBean(
388                         ctx,
389                         customArgs[0],
390                         CustomGatewayFilterFactory.class);
391                 filter = factory.getOrder().
392                         map(order -> (GatewayFilter) new OrderedGatewayFilter(factory.apply(customConsumer), order)).
393                         orElseGet(() -> factory.apply(customConsumer));
394                 break;
395 
396             default:
397                 filter = null;
398         }
399 
400         if (filter == null) {
401             throw new IllegalArgumentException("Could not translate " + gwfilter);
402         }
403 
404         return filter instanceof Ordered ? filter : new OrderedGatewayFilter(filter, 0);
405     }
406 
407     protected AsyncPredicate<ServerWebExchange> toPredicate(final SRARoutePredicate gwpredicate, final boolean negate)
408             throws ClassNotFoundException, NumberFormatException {
409 
410         AsyncPredicate<ServerWebExchange> predicate;
411         switch (gwpredicate.getFactory()) {
412             case AFTER:
413                 predicate = ctx.getBean(AfterRoutePredicateFactory.class).
414                         applyAsync(c -> c.setDatetime(ZonedDateTime.parse(gwpredicate.getArgs().trim())));
415                 break;
416 
417             case BEFORE:
418                 predicate = ctx.getBean(BeforeRoutePredicateFactory.class).
419                         applyAsync(c -> c.setDatetime(ZonedDateTime.parse(gwpredicate.getArgs().trim())));
420                 break;
421 
422             case BETWEEN:
423                 String[] betweenArgs = gwpredicate.getArgs().split(",");
424                 predicate = ctx.getBean(BetweenRoutePredicateFactory.class).
425                         applyAsync(c -> c.setDatetime1(ZonedDateTime.parse(betweenArgs[0].trim())).
426                         setDatetime2(ZonedDateTime.parse(betweenArgs[1].trim())));
427                 break;
428 
429             case COOKIE:
430                 String[] cookieArgs = gwpredicate.getArgs().split(",");
431                 predicate = ctx.getBean(CookieRoutePredicateFactory.class).
432                         applyAsync(c -> c.setName(cookieArgs[0].trim()).
433                         setRegexp(cookieArgs[1].trim()));
434                 break;
435 
436             case HEADER:
437                 String[] headerArgs = gwpredicate.getArgs().split(",");
438                 predicate = ctx.getBean(HeaderRoutePredicateFactory.class).
439                         applyAsync(c -> c.setHeader(headerArgs[0].trim()).
440                         setRegexp(headerArgs[1].trim()));
441                 break;
442 
443             case HOST:
444                 String[] hostArgs = gwpredicate.getArgs().split(",");
445                 predicate = ctx.getBean(HostRoutePredicateFactory.class).
446                         applyAsync(c -> c.setPatterns(List.of(hostArgs)));
447                 break;
448 
449             case METHOD:
450                 String[] methodArgs = gwpredicate.getArgs().split(",");
451                 predicate = ctx.getBean(MethodRoutePredicateFactory.class).
452                         applyAsync(c -> c.setMethods(
453                         Stream.of(methodArgs).map(arg -> HttpMethod.resolve(arg.trim())).toArray(HttpMethod[]::new)));
454                 break;
455 
456             case PATH:
457                 String[] pathArgs = gwpredicate.getArgs().split(",");
458                 predicate = ctx.getBean(PathRoutePredicateFactory.class).
459                         applyAsync(c -> c.setPatterns(List.of(pathArgs)));
460                 break;
461 
462             case QUERY:
463                 String[] queryArgs = gwpredicate.getArgs().split(",");
464                 Consumer<QueryRoutePredicateFactory.Config> queryConsumer =
465                         queryArgs.length > 1
466                                 ? c -> c.setParam(queryArgs[0].trim()).setRegexp(queryArgs[1].trim())
467                                 : c -> c.setParam(queryArgs[0].trim());
468                 predicate = ctx.getBean(QueryRoutePredicateFactory.class).
469                         applyAsync(queryConsumer);
470                 break;
471 
472             case REMOTE_ADDR:
473                 String[] remoteAddrArgs = gwpredicate.getArgs().split(",");
474                 predicate = ctx.getBean(RemoteAddrRoutePredicateFactory.class).
475                         applyAsync(c -> c.setSources(List.of(remoteAddrArgs)));
476                 break;
477 
478             case WEIGHT:
479                 String[] weigthArgs = gwpredicate.getArgs().split(",");
480                 AtomicInteger weight = new AtomicInteger();
481                 try {
482                     weight.set(Integer.valueOf(weigthArgs[1].trim()));
483                 } catch (NumberFormatException e) {
484                     LOG.error("Unexpected argument value: {}", weigthArgs[1].trim(), e);
485                     weight.set(0);
486                 }
487                 predicate = ctx.getBean(WeightRoutePredicateFactory.class).
488                         applyAsync(c -> c.setGroup(weigthArgs[0].trim()).
489                         setWeight(weight.get()));
490                 break;
491 
492             case CUSTOM:
493                 String[] customArgs = gwpredicate.getArgs().split(";");
494                 predicate = ApplicationContextUtils.getOrCreateBean(
495                         ctx,
496                         customArgs[0],
497                         CustomRoutePredicateFactory.class).
498                         applyAsync(c -> c.setData(customArgs[1]));
499                 break;
500 
501             default:
502                 predicate = null;
503         }
504 
505         if (predicate == null) {
506             throw new IllegalArgumentException("Could not translate predicate " + gwpredicate);
507         }
508 
509         return negate ? predicate.negate() : predicate;
510     }
511 
512     protected Route.AsyncBuilder toRoute(final SRARouteTO gwroute) {
513         Route.AsyncBuilder builder = new Route.AsyncBuilder().
514                 id(gwroute.getKey()).order(gwroute.getOrder()).uri(gwroute.getTarget());
515 
516         if (gwroute.getPredicates().isEmpty()) {
517             builder.predicate(exchange -> true);
518         } else {
519             gwroute.getPredicates().forEach(gwpredicate -> {
520                 if (builder.getPredicate() == null) {
521                     try {
522                         builder.asyncPredicate(toPredicate(gwpredicate, gwpredicate.isNegate()));
523                     } catch (Exception e) {
524                         LOG.error("Could not translate {}, skipping", gwpredicate, e);
525                     }
526                 } else {
527                     try {
528                         switch (gwpredicate.getCond()) {
529                             case OR:
530                                 builder.or(toPredicate(gwpredicate, gwpredicate.isNegate()));
531                                 break;
532 
533                             case AND:
534                             default:
535                                 builder.and(toPredicate(gwpredicate, gwpredicate.isNegate()));
536                         }
537                     } catch (Exception e) {
538                         LOG.error("Could not translate {}, skipping", gwpredicate, e);
539                     }
540                 }
541             });
542         }
543 
544         if (!gwroute.getFilters().isEmpty()) {
545             builder.filters(gwroute.getFilters().stream().
546                     map(gwfilter -> {
547                         try {
548                             return toFilter(gwroute, gwfilter);
549                         } catch (Exception e) {
550                             LOG.error("Could not translate {}, skipping", gwfilter, e);
551                             return null;
552                         }
553                     }).
554                     filter(Objects::nonNull).
555                     collect(Collectors.toList()));
556         }
557 
558         return builder;
559     }
560 
561     public List<Route.AsyncBuilder> fetch() {
562         synchronized (this) {
563             if (client == null) {
564                 try {
565                     client = new SyncopeClientFactoryBean().
566                             setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()).
567                             setUseCompression(useGZIPCompression).
568                             create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey));
569                 } catch (Exception e) {
570                     LOG.error("Could not init SyncopeClient", e);
571                     return List.of();
572                 }
573             }
574         }
575 
576         synchronized (routeTOs) {
577             routeTOs.clear();
578             routeTOs.addAll(client.getService(SRARouteService.class).list());
579         }
580 
581         return routeTOs.stream().map(this::toRoute).collect(Collectors.toList());
582     }
583 
584     public List<SRARouteTO> getRouteTOs() {
585         return routeTOs;
586     }
587 }