1515import java .io .IOException ;
1616import java .io .InputStream ;
1717import java .util .Properties ;
18+ import java .util .concurrent .locks .ReentrantLock ;
1819
1920import org .eclipse .jdt .annotation .NonNullByDefault ;
2021import org .eclipse .jdt .annotation .Nullable ;
3132@ NonNullByDefault
3233public class FroniusHttpUtil {
3334 private static final Logger LOGGER = LoggerFactory .getLogger (FroniusHttpUtil .class );
35+ private final ReentrantLock requestLock = new ReentrantLock ();
36+
37+ private enum RequestMode {
38+ CONTROL ,
39+ POLLING
40+ }
41+
42+ @ FunctionalInterface
43+ interface RequestExecutor {
44+ @ Nullable
45+ String execute (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
46+ @ Nullable InputStream content , @ Nullable String contentType , int timeout ) throws IOException ;
47+ }
3448
3549 /**
3650 * Issue a HTTP request and retry on failure.
@@ -41,8 +55,7 @@ public class FroniusHttpUtil {
4155 * @return the response body
4256 * @throws FroniusCommunicationException when the request execution failed or interrupted
4357 */
44- public static synchronized String executeUrl (HttpMethod httpMethod , String url , int timeout )
45- throws FroniusCommunicationException {
58+ public String executeUrl (HttpMethod httpMethod , String url , int timeout ) throws FroniusCommunicationException {
4659 return executeUrl (httpMethod , url , null , null , null , timeout );
4760 }
4861
@@ -59,17 +72,71 @@ public static synchronized String executeUrl(HttpMethod httpMethod, String url,
5972 * @return the response body
6073 * @throws FroniusCommunicationException when the request execution failed or interrupted
6174 */
62- public static synchronized String executeUrl (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
75+ public String executeUrl (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
76+ @ Nullable InputStream content , @ Nullable String contentType , int timeout )
77+ throws FroniusCommunicationException {
78+ return executeUrl (httpMethod , url , httpHeaders , content , contentType , timeout , this ::executeRequest );
79+ }
80+
81+ /**
82+ * Issue a polling HTTP request and skip it when another request for the same bridge is already running.
83+ *
84+ * @param httpMethod the HTTP method to use
85+ * @param url the url to execute
86+ * @param timeout the socket timeout in milliseconds to wait for data
87+ * @return the response body
88+ * @throws FroniusCommunicationException when the request execution failed or interrupted
89+ */
90+ public String executePollingUrl (HttpMethod httpMethod , String url , int timeout )
91+ throws FroniusCommunicationException {
92+ return executePollingUrl (httpMethod , url , null , null , null , timeout );
93+ }
94+
95+ /**
96+ * Issue a polling HTTP request and skip it when another request for the same bridge is already running.
97+ *
98+ * @param httpMethod the HTTP method to use
99+ * @param url the url to execute
100+ * @param httpHeaders optional http request headers which has to be sent within request
101+ * @param content the content to be sent to the given <code>url</code> or <code>null</code> if no content should be
102+ * sent.
103+ * @param contentType the content type of the given <code>content</code>
104+ * @param timeout the socket timeout in milliseconds to wait for data
105+ * @return the response body
106+ * @throws FroniusCommunicationException when the request execution failed or interrupted
107+ */
108+ public String executePollingUrl (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
63109 @ Nullable InputStream content , @ Nullable String contentType , int timeout )
64110 throws FroniusCommunicationException {
111+ return executePollingUrl (httpMethod , url , httpHeaders , content , contentType , timeout , this ::executeRequest );
112+ }
113+
114+ String executeUrl (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
115+ @ Nullable InputStream content , @ Nullable String contentType , int timeout , RequestExecutor requestExecutor )
116+ throws FroniusCommunicationException {
117+ return executeUrl (httpMethod , url , httpHeaders , content , contentType , timeout , RequestMode .CONTROL ,
118+ requestExecutor );
119+ }
120+
121+ String executePollingUrl (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
122+ @ Nullable InputStream content , @ Nullable String contentType , int timeout , RequestExecutor requestExecutor )
123+ throws FroniusCommunicationException {
124+ return executeUrl (httpMethod , url , httpHeaders , content , contentType , timeout , RequestMode .POLLING ,
125+ requestExecutor );
126+ }
127+
128+ private String executeUrl (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
129+ @ Nullable InputStream content , @ Nullable String contentType , int timeout , RequestMode requestMode ,
130+ RequestExecutor requestExecutor ) throws FroniusCommunicationException {
131+ acquireLock (requestLock , requestMode , url );
132+ LOGGER .debug ("Executing {} request against {}" , requestMode , url );
65133 int attemptCount = 1 ;
66134 try {
67135 while (true ) {
68136 Throwable lastException = null ;
69137 String result = null ;
70138 try {
71- result = HttpUtil .executeUrl (httpMethod .asString (), url , httpHeaders , content , contentType ,
72- timeout );
139+ result = requestExecutor .execute (httpMethod , url , httpHeaders , content , contentType , timeout );
73140 } catch (IOException e ) {
74141 // HttpUtil::executeUrl wraps InterruptedException into IOException.
75142 // Unwrap and rethrow it so that we don't retry on InterruptedException
@@ -99,6 +166,30 @@ public static synchronized String executeUrl(HttpMethod httpMethod, String url,
99166 } catch (InterruptedException e ) {
100167 Thread .currentThread ().interrupt ();
101168 throw new FroniusCommunicationException ("Interrupted" , e );
169+ } finally {
170+ requestLock .unlock ();
102171 }
103172 }
173+
174+ private static void acquireLock (ReentrantLock requestLock , RequestMode requestMode , String url )
175+ throws FroniusCommunicationException {
176+ try {
177+ if (requestMode == RequestMode .POLLING ) {
178+ if (!requestLock .tryLock ()) {
179+ throw new FroniusPollingSkipException ("Skipping polling request to '" + url
180+ + "' because another request for this Fronius bridge is still running" );
181+ }
182+ } else {
183+ requestLock .lockInterruptibly ();
184+ }
185+ } catch (InterruptedException e ) {
186+ Thread .currentThread ().interrupt ();
187+ throw new FroniusCommunicationException ("Interrupted" , e );
188+ }
189+ }
190+
191+ private @ Nullable String executeRequest (HttpMethod httpMethod , String url , @ Nullable Properties httpHeaders ,
192+ @ Nullable InputStream content , @ Nullable String contentType , int timeout ) throws IOException {
193+ return HttpUtil .executeUrl (httpMethod .asString (), url , httpHeaders , content , contentType , timeout );
194+ }
104195}
0 commit comments