1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.tooling.commons.packmgr;
21
22 import java.io.IOException;
23 import java.net.URI;
24 import java.net.URISyntaxException;
25 import java.security.KeyManagementException;
26 import java.security.KeyStoreException;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.List;
29 import java.util.regex.Pattern;
30
31 import javax.net.ssl.SSLContext;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.lang3.time.DateUtils;
35 import org.apache.http.HttpException;
36 import org.apache.http.HttpHost;
37 import org.apache.http.HttpRequest;
38 import org.apache.http.HttpRequestInterceptor;
39 import org.apache.http.HttpResponse;
40 import org.apache.http.auth.AuthScope;
41 import org.apache.http.auth.AuthState;
42 import org.apache.http.auth.Credentials;
43 import org.apache.http.auth.UsernamePasswordCredentials;
44 import org.apache.http.client.CredentialsProvider;
45 import org.apache.http.client.methods.HttpRequestBase;
46 import org.apache.http.client.protocol.HttpClientContext;
47 import org.apache.http.conn.ConnectionKeepAliveStrategy;
48 import org.apache.http.conn.ssl.NoopHostnameVerifier;
49 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
50 import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
51 import org.apache.http.impl.auth.BasicScheme;
52 import org.apache.http.impl.client.BasicCredentialsProvider;
53 import org.apache.http.impl.client.CloseableHttpClient;
54 import org.apache.http.impl.client.HttpClientBuilder;
55 import org.apache.http.impl.client.HttpClients;
56 import org.apache.http.protocol.HttpContext;
57 import org.apache.http.ssl.SSLContextBuilder;
58 import org.jdom2.Document;
59 import org.jetbrains.annotations.NotNull;
60 import org.jetbrains.annotations.Nullable;
61 import org.json.JSONObject;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import io.wcm.tooling.commons.packmgr.httpaction.BundleStatus;
66 import io.wcm.tooling.commons.packmgr.httpaction.BundleStatusCall;
67 import io.wcm.tooling.commons.packmgr.httpaction.HttpCall;
68 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerHtmlCall;
69 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerHtmlMessageCall;
70 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerInstallStatus;
71 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerInstallStatusCall;
72 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerJsonCall;
73 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerStatusCall;
74 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerXmlCall;
75 import io.wcm.tooling.commons.packmgr.util.HttpClientUtil;
76
77
78
79
80 public final class PackageManagerHelper {
81
82
83
84
85 public static final String CRX_PACKAGE_EXISTS_ERROR_MESSAGE_PREFIX = "Package already exists: ";
86
87 private static final String HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS = PackageManagerHelper.class.getName() + "_PreemptiveAuthenticationCreds";
88 private static final String HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN = PackageManagerHelper.class.getName() + "_oauth2AccessToken";
89
90 private final PackageManagerProperties props;
91
92 private static final Logger log = LoggerFactory.getLogger(PackageManagerHelper.class);
93
94
95
96
97 public PackageManagerHelper(PackageManagerProperties props) {
98 this.props = props;
99 }
100
101
102
103
104
105 public @NotNull CloseableHttpClient getHttpClient() {
106 HttpClientBuilder httpClientBuilder = HttpClients.custom()
107 .setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
108 @Override
109 public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
110
111 return 1;
112 }
113 })
114 .addInterceptorFirst(new HttpRequestInterceptor() {
115 @Override
116 public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
117 Credentials credentials = (Credentials)context.getAttribute(HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS);
118 if (credentials != null) {
119
120 AuthState authState = (AuthState)context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
121 authState.update(new BasicScheme(), credentials);
122 }
123 String oauth2AccessToken = (String)context.getAttribute(HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN);
124 if (oauth2AccessToken != null) {
125
126 request.setHeader("Authorization", "Bearer " + oauth2AccessToken);
127 }
128 }
129 });
130
131
132 if (props.isRelaxedSSLCheck()) {
133 try {
134 SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
135 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
136 httpClientBuilder.setSSLSocketFactory(sslsf);
137 }
138 catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException ex) {
139 throw new PackageManagerException("Could not set relaxedSSLCheck", ex);
140 }
141 }
142
143
144 Proxy proxy = getProxyForUrl(props.getPackageManagerUrl());
145 if (proxy != null) {
146 httpClientBuilder.setProxy(new HttpHost(proxy.getHost(), proxy.getPort(), proxy.getProtocol()));
147 }
148
149 return httpClientBuilder.build();
150 }
151
152
153
154
155
156 public @NotNull HttpClientContext getPackageManagerHttpClientContext() {
157 return getHttpClientContext(props.getPackageManagerUrl(),
158 props.getUserId(), props.getPassword(), props.getOAuth2AccessToken());
159 }
160
161
162
163
164
165 public @Nullable HttpClientContext getConsoleHttpClientContext() {
166 String bundleStatusUrl = props.getBundleStatusUrl();
167 if (bundleStatusUrl == null) {
168 return null;
169 }
170 return getHttpClientContext(bundleStatusUrl,
171 props.getConsoleUserId(), props.getConsolePassword(), props.getConsoleOAuth2AccessToken());
172 }
173
174 private @NotNull HttpClientContext getHttpClientContext(String url, String userId, String password, String oauth2AccessToken) {
175 URI uri;
176 try {
177 uri = new URI(url);
178 }
179 catch (URISyntaxException ex) {
180 throw new PackageManagerException("Invalid url: " + url, ex);
181 }
182
183 final CredentialsProvider credsProvider = new BasicCredentialsProvider();
184 HttpClientContext context = new HttpClientContext();
185 context.setCredentialsProvider(credsProvider);
186
187 if (StringUtils.isNotBlank(oauth2AccessToken)) {
188 context.setAttribute(HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN, oauth2AccessToken);
189 }
190 else {
191
192 final AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort());
193 final Credentials credentials = new UsernamePasswordCredentials(userId, password);
194 credsProvider.setCredentials(authScope, credentials);
195 context.setAttribute(HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS, credentials);
196 }
197
198
199 context.setRequestConfig(HttpClientUtil.buildRequestConfig(props));
200
201
202 Proxy proxy = getProxyForUrl(url);
203 if (proxy != null && proxy.useAuthentication()) {
204 AuthScope proxyAuthScope = new AuthScope(proxy.getHost(), proxy.getPort());
205 Credentials proxyCredentials = new UsernamePasswordCredentials(proxy.getUsername(), proxy.getPassword());
206 credsProvider.setCredentials(proxyAuthScope, proxyCredentials);
207 }
208
209 return context;
210 }
211
212
213
214
215
216
217 private Proxy getProxyForUrl(String requestUrl) {
218 List<Proxy> proxies = props.getProxies();
219 if (proxies == null || proxies.isEmpty()) {
220 return null;
221 }
222 final URI uri = URI.create(requestUrl);
223 for (Proxy proxy : proxies) {
224 if (!proxy.isNonProxyHost(uri.getHost())) {
225 return proxy;
226 }
227 }
228 return null;
229 }
230
231
232
233
234
235
236
237 @SuppressWarnings("PMD.GuardLogStatement")
238 private <T> T executeHttpCallWithRetry(HttpCall<T> call, int runCount) {
239 try {
240 return call.execute();
241 }
242 catch (PackageManagerHttpActionException ex) {
243
244 if (runCount < props.getRetryCount()) {
245 log.warn("ERROR: {}", ex.getMessage());
246 log.debug("HTTP call failed.", ex);
247 log.warn("---------------");
248
249 StringBuilder msg = new StringBuilder();
250 msg.append("HTTP call failed, try again (" + (runCount + 1) + "/" + props.getRetryCount() + ")");
251 if (props.getRetryDelaySec() > 0) {
252 msg.append(" after " + props.getRetryDelaySec() + " second(s)");
253 }
254 msg.append("...");
255 log.warn(msg.toString());
256 if (props.getRetryDelaySec() > 0) {
257 try {
258 Thread.sleep(props.getRetryDelaySec() * DateUtils.MILLIS_PER_SECOND);
259 }
260 catch (InterruptedException ex1) {
261
262 }
263 }
264 return executeHttpCallWithRetry(call, runCount + 1);
265 }
266 else {
267 throw ex;
268 }
269 }
270 }
271
272
273
274
275
276
277
278
279 public JSONObject executePackageManagerMethodJson(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
280 PackageManagerJsonCall call = new PackageManagerJsonCall(httpClient, context, method);
281 return executeHttpCallWithRetry(call, 0);
282 }
283
284
285
286
287
288
289
290
291 public Document executePackageManagerMethodXml(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
292 PackageManagerXmlCall call = new PackageManagerXmlCall(httpClient, context, method);
293 return executeHttpCallWithRetry(call, 0);
294 }
295
296
297
298
299
300
301
302
303 public String executePackageManagerMethodHtml(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
304 PackageManagerHtmlCall call = new PackageManagerHtmlCall(httpClient, context, method);
305 return executeHttpCallWithRetry(call, 0);
306 }
307
308
309
310
311
312
313
314 public void executePackageManagerMethodHtmlOutputResponse(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
315 PackageManagerHtmlMessageCall call = new PackageManagerHtmlMessageCall(httpClient, context, method, props);
316 executeHttpCallWithRetry(call, 0);
317 }
318
319
320
321
322
323
324
325
326 public void executePackageManagerMethodStatus(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
327 PackageManagerStatusCall call = new PackageManagerStatusCall(httpClient, context, method);
328 executeHttpCallWithRetry(call, 0);
329 }
330
331
332
333
334
335
336 @SuppressWarnings("PMD.GuardLogStatement")
337 public void waitForBundlesActivation(CloseableHttpClient httpClient, HttpClientContext context) {
338 if (StringUtils.isBlank(props.getBundleStatusUrl())) {
339 log.debug("Skipping check for bundle activation state because no bundleStatusURL is defined.");
340 return;
341 }
342
343 final int WAIT_INTERVAL_SEC = 3;
344 final long CHECK_RETRY_COUNT = props.getBundleStatusWaitLimitSec() / WAIT_INTERVAL_SEC;
345
346 log.info("Check bundle activation status...");
347 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
348 BundleStatusCall call = new BundleStatusCall(httpClient, context, props.getBundleStatusUrl(),
349 props.getBundleStatusWhitelistBundleNames());
350 BundleStatus bundleStatus = executeHttpCallWithRetry(call, 0);
351
352 boolean instanceReady = true;
353
354
355 if (!bundleStatus.isAllBundlesRunning()) {
356 log.info("Bundles starting/stopping: {} - wait {} sec (max. {} sec) ...",
357 bundleStatus.getStatusLineCompact(), WAIT_INTERVAL_SEC, props.getBundleStatusWaitLimitSec());
358 sleep(WAIT_INTERVAL_SEC);
359 instanceReady = false;
360 }
361
362
363 if (instanceReady) {
364 for (Pattern blacklistBundleNamePattern : props.getBundleStatusBlacklistBundleNames()) {
365 String bundleSymbolicName = bundleStatus.getMatchingBundle(blacklistBundleNamePattern);
366 if (bundleSymbolicName != null) {
367 log.info("Bundle '{}' is still deployed - wait {} sec (max. {} sec) ...",
368 bundleSymbolicName, WAIT_INTERVAL_SEC, props.getBundleStatusWaitLimitSec());
369 sleep(WAIT_INTERVAL_SEC);
370 instanceReady = false;
371 break;
372 }
373 }
374 }
375
376
377 if (instanceReady) {
378 break;
379 }
380 }
381 }
382
383
384
385
386
387
388 @SuppressWarnings("PMD.GuardLogStatement")
389 public void waitForPackageManagerInstallStatusFinished(CloseableHttpClient httpClient, HttpClientContext context) {
390 if (StringUtils.isBlank(props.getPackageManagerInstallStatusURL())) {
391 log.debug("Skipping check for package manager install state because no packageManagerInstallStatusURL is defined.");
392 return;
393 }
394
395 final int WAIT_INTERVAL_SEC = 3;
396 final long CHECK_RETRY_COUNT = props.getPackageManagerInstallStatusWaitLimitSec() / WAIT_INTERVAL_SEC;
397
398 log.info("Check package manager installation status...");
399 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
400 PackageManagerInstallStatusCall call = new PackageManagerInstallStatusCall(httpClient, context,
401 props.getPackageManagerInstallStatusURL());
402 PackageManagerInstallStatus packageManagerStatus = executeHttpCallWithRetry(call, 0);
403
404 boolean instanceReady = true;
405
406
407 if (!packageManagerStatus.isFinished()) {
408 log.info("Packager manager not ready: {} packages left for installation - wait {} sec (max. {} sec) ...",
409 packageManagerStatus.getItemCount(), WAIT_INTERVAL_SEC, props.getPackageManagerInstallStatusWaitLimitSec());
410 sleep(WAIT_INTERVAL_SEC);
411 instanceReady = false;
412 }
413
414
415 if (instanceReady) {
416 break;
417 }
418 }
419 }
420
421 private void sleep(int sec) {
422 try {
423 Thread.sleep(sec * DateUtils.MILLIS_PER_SECOND);
424 }
425 catch (InterruptedException e) {
426
427 }
428 }
429
430 }