View Javadoc
1   /*
2    * Copyright (c) 2023. Roland T. Lichti, Kaiserpfalz EDV-Service.
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16   */
17  
18  package de.kaiserpfalzedv.services.sms77.client;
19  
20  import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
21  
22  import java.util.Set;
23  
24  import org.springframework.cloud.openfeign.FeignClient;
25  import org.springframework.web.bind.annotation.RequestMapping;
26  import org.springframework.web.bind.annotation.RequestMethod;
27  import org.springframework.web.bind.annotation.RequestParam;
28  
29  import de.kaiserpfalzedv.services.sms77.model.Balance;
30  import de.kaiserpfalzedv.services.sms77.model.NumberFormatCheckResult;
31  import de.kaiserpfalzedv.services.sms77.model.Sms;
32  import de.kaiserpfalzedv.services.sms77.model.SmsResult;
33  import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
34  import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
35  import io.github.resilience4j.retry.annotation.Retry;
36  import io.micrometer.core.annotation.Counted;
37  import io.micrometer.core.annotation.Timed;
38  import jakarta.validation.constraints.NotBlank;
39  import jakarta.validation.constraints.NotNull;
40  import jakarta.validation.constraints.Size;
41  
42  /**
43   * <p>
44   * Sms77Client -- The client for accessing the webservice.
45   * </p>
46   *
47   * <p>
48   * This is the client for accessing the API of the sms77 paid webservice. You
49   * need an Api-Key. This client has a
50   * quarkus application.yaml included which will rely on certain environment
51   * variables to be set. These are:
52   * </p>
53   *
54   * <dl>
55   * <dt>SMS77_API_URL</dt>
56   * <dd><em>(optional)</em>The URI for the SMS77.io api. Normally there is no
57   * reason to give another URI than
58   * {@literal https://gateway.sms77.io}. And that URI is the default when nothing
59   * else is specified./</dd>
60   * <dt>SMS77_API_KEY</dt>
61   * <dd>The API key from sms77.io. For development you should generate a sandbox
62   * api key to cut
63   * costs - but your mileage may vary.</dd>
64   * </dl>
65   *
66   * <p>
67   * For the time being the API does not throw any sms77 specific exceptions since
68   * the sms77 API always returns
69   * HTTP 200. You have to check the return objects of the calls to find out if
70   * there has something happened.
71   * </p>
72   *
73   * <p>
74   * <em
75   * >TODO 2023-01-22 rlichti Implement a filter to read the objects and generate
76   * matching exceptions.
77   * <br/>
78   * NOTE: I don't have a direct need for this, but it would be a much nicer
79   * interface for accessing the sms77 api.
80   * </em>
81   * </p>
82   *
83   *
84   *
85   * @author rlichti {@literal <rlichti@kaiserpfalz-edv.de>}
86   * @since 3.0.0 2023-01-17
87   */
88  @FeignClient(name = "sms77", configuration = Sms77ClientConfig.class, path = "/api")
89  @SuppressWarnings("JavadocLinkAsPlainText")
90  @RateLimiter(name = "sms77client")
91  public interface Sms77Client {
92      /**
93       * Sends the SMS.
94       *
95       * @param sms The SMS to be sent. The same SMS can address multiple users.
96       * @return The result of the SMS sending.
97       */
98      @RequestMapping(method = RequestMethod.POST, value = "/sms", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
99      @Timed("sms77.send-sms-json.time")
100     @Counted("sms77.send-sms-json.count")
101     @Retry(name = "sendSMS")
102     @CircuitBreaker(name = "sendSMS")
103 //        delay = 1000, maxDuration = 5000, retryOn = {            Sms77RateLimitException.class }, abortOn = Sms77Exception.class)
104 //    @CircuitBreaker(failOn = Sms77Exception.class, requestVolumeThreshold = 5)
105     SmsResult sendSMS(@NotNull final Sms sms);
106 
107     /**
108      * Sends the given text to the numbers specified.
109      *
110      * @param number A set of destinations for the SMS.
111      * @param text   The text of the SMS.
112      * @return
113      */
114     @RequestMapping(method = RequestMethod.POST, value = "/sms", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
115     @Timed("sms77.send-sms-query.time")
116     @Counted("sms77.send-sms-query.count")
117     @Retry(name = "sendSMS")
118     @CircuitBreaker(name = "sendSMS")
119     SmsResult sendSMS(
120             @Size(min = 1, max = 10) @RequestParam("to") @NotNull final Set<String> number,
121 
122             @Size(max = 1520) @RequestParam("text") @NotNull final String text);
123 
124     /**
125      * To check the current credits to use on this API you can call the balance and
126      * get the current credits.
127      *
128      * @return The current account balance of your sms77.io account.
129      */
130     @Timed("sms77.balance.time")
131     @Counted("sms77.balance.count")
132     @Retry(name = "balanceSMS")
133     @CircuitBreaker(name = "balanceSMS")
134     @RequestMapping(method = RequestMethod.GET, value = "/balance", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
135     Balance balance();
136 
137     /**
138      * <p>
139      * Check if the given numbers are formatted correctly. There will be no check,
140      * if the numbers are valid. This
141      * <p>
142      * check only validates the number format and not if the number is used or even
143      * active.
144      * </p>
145      *
146      * <p>
147      * This is a very ugly API call since the numbers need to be formated as single
148      * string with a comma as
149      * delimiter.
150      * </p>
151      *
152      * <p>
153      * Consider something as Set.of("49123231","124323131").join(",") ...
154      * </p>
155      *
156      * @param numbersWithComma The numbers to check, delimited by a comma.
157      * @return The format check result.
158      */
159     @Timed("sms77.number-format-check.multi.time")
160     @Counted("sms77.number-format-check.multi.count")
161     @Retry(name = "lookupSMS")
162     @CircuitBreaker(name = "lookupSMS")
163     @RequestMapping(method = RequestMethod.GET, value = "/lookup", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
164     Set<NumberFormatCheckResult> checkMultipleNumberFormats(
165             @RequestParam("number") @NotBlank final String numbersWithComma);
166 
167     /**
168      * <p>
169      * Checks the format of a single number.
170      * </p>
171      *
172      * <p>
173      * This function only checks, if the number format is correct. There is no check
174      * if the number is assigned or
175      * even activ.
176      * </p>
177      *
178      * @param number the number which format should be checked.
179      * @return The format check result.
180      */
181     @Timed("sms77.number-format-check.multi.time")
182     @Counted("sms77.number-format-check.multi.count")
183     @Retry(name = "checkNumberFormatSMS")
184     @CircuitBreaker(name = "checkNumberFormatSMS")
185     @RequestMapping(method = RequestMethod.GET, value = "/lookup", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
186     NumberFormatCheckResult checkNumberFormat(@RequestParam("number") @NotBlank final String number);
187 }