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.common.keymaster.client.self;
20  
21  import java.util.List;
22  import java.util.Map;
23  import java.util.concurrent.CompletionStage;
24  import javax.ws.rs.Path;
25  import javax.ws.rs.client.Entity;
26  import javax.ws.rs.core.MediaType;
27  import javax.ws.rs.core.Response;
28  import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
29  import org.apache.syncope.common.keymaster.client.api.KeymasterException;
30  import org.apache.syncope.common.keymaster.client.api.ServiceOps;
31  import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
32  import org.apache.syncope.common.keymaster.rest.api.service.NetworkServiceService;
33  import org.apache.syncope.common.keymaster.rest.api.service.NetworkServiceService.Action;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  import org.springframework.util.backoff.BackOffExecution;
37  import org.springframework.util.backoff.ExponentialBackOff;
38  
39  public class SelfKeymasterServiceOps extends SelfKeymasterOps implements ServiceOps {
40  
41      private static final Logger LOG = LoggerFactory.getLogger(ServiceOps.class);
42  
43      private final int maxRetries;
44  
45      private final String path;
46  
47      public SelfKeymasterServiceOps(final JAXRSClientFactoryBean clientFactory, final int maxRetries) {
48          super(clientFactory);
49          this.maxRetries = maxRetries;
50          this.path = NetworkServiceService.class.getAnnotation(Path.class).value();
51      }
52  
53      @Override
54      public List<NetworkService> list(final NetworkService.Type serviceType) {
55          try {
56              return client(NetworkServiceService.class, Map.of()).list(serviceType);
57          } catch (KeymasterException e) {
58              throw e;
59          } catch (Exception e) {
60              throw new KeymasterException(e);
61          }
62      }
63  
64      @Override
65      public NetworkService get(final NetworkService.Type serviceType) {
66          try {
67              return client(NetworkServiceService.class, Map.of()).get(serviceType);
68          } catch (KeymasterException e) {
69              throw e;
70          } catch (Exception e) {
71              throw new KeymasterException(e);
72          }
73      }
74  
75      private void handleRetry(
76              final NetworkService service,
77              final Action action,
78              final int retries,
79              final BackOffExecution backOffExecution) {
80  
81          try {
82              if (action == Action.register && retries > 0) {
83                  long nextBackoff = backOffExecution.nextBackOff();
84  
85                  LOG.debug("Still {} retries available for {}; waiting for {} ms", retries, service, nextBackoff);
86                  try {
87                      Thread.sleep(nextBackoff);
88                  } catch (InterruptedException e) {
89                      // ignore
90                  }
91  
92                  retry(completionStage(action, service), service, action, retries - 1, backOffExecution);
93              } else {
94                  LOG.debug("No more retries {} for {}", action, service);
95              }
96          } catch (Throwable t) {
97              LOG.error("Could not continue {} for {}, aborting", action, service, t);
98          }
99      }
100 
101     private void retry(
102             final CompletionStage<Response> completionStage,
103             final NetworkService service,
104             final Action action,
105             final int retries,
106             final BackOffExecution backOffExecution) {
107 
108         completionStage.whenComplete((response, throwable) -> {
109             if (throwable == null && response.getStatus() < 300) {
110                 LOG.info("{} successfully " + action + "ed", service);
111             } else {
112                 LOG.error("Could not " + action + " {}", service, throwable);
113 
114                 handleRetry(service, action, retries, backOffExecution);
115             }
116         }).exceptionally(throwable -> {
117             LOG.error("Could not " + action + " {}", service, throwable);
118 
119             handleRetry(service, action, retries, backOffExecution);
120 
121             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
122         });
123     }
124 
125     private CompletionStage<Response> completionStage(final Action action, final NetworkService service) {
126         return rx(path + "?action=" + action.name()).post(Entity.entity(service, MediaType.APPLICATION_JSON));
127     }
128 
129     @Override
130     public void register(final NetworkService service) {
131         retry(completionStage(Action.register, service),
132                 service, Action.register, maxRetries, new ExponentialBackOff(5000L, 1.5).start());
133     }
134 
135     @Override
136     public void unregister(final NetworkService service) {
137         retry(completionStage(Action.unregister, service),
138                 service, Action.unregister, maxRetries, new ExponentialBackOff(5000L, 1.5).start());
139     }
140 }